重做设置界面并修复登录自启:页签单选、滚轮归属、任务缺失判定、发布去掉自带运行时
设置窗口 - 改为独立非模态窗口(进任务栏、可最小化),不再被托盘浮窗挡住导致点不到 - 位置改为按屏幕可用区域居中裁剪,不再默认落在右下角 - 三个页签重构为卡片式布局:统一卡片内边距、页签行独立成行、正文左右留白、 帮助文字按实际换行测量高度、标题栏与底栏加分隔线 - 直连 DNS 复选框、下拉框、滚动条改为自绘控件,配色与整体统一 页签 - PanelChip 的选中/强调改为值变化即重绘,修掉“切换页签后旧页签仍显示为激活、 鼠标移上去才更新”的陈旧绘制 滚轮 - 输入框滚不动自己时把滚轮交给外层页面,单行输入框不再吞掉滚轮事件 登录自启 - 改用任务 XML + schtasks 写入,COM 失败时用 schtasks 复核;写入后读回校验 - 任务不存在按 HRESULT 判定(0x80070002 / 0x80070003 / 0x8004130F),修掉 “关闭登录自启必定报错并回滚”(晚绑定 COM 抛的是 FileNotFoundException) - 状态读取 COM 失败时回退到 schtasks,失败按异常类型 + HResult 去重记录 诊断 - 界面报错统一写日志;控制器记录自启读写结果;启动自检记录管理员/自启/进程/参数 发布 - 去掉 -SelfContained 分支,只保留单文件运行时依赖包,脚本与文档同步 - 新增 tools/unblock-publish.ps1,清除发布产物的网络来源标记 测试 - 新增登录自启任务 XML 用例与“任务缺失按 HRESULT 判定”回归测试(46 → 47),探测 16/16
This commit is contained in:
+90
-4
@@ -1,8 +1,94 @@
|
||||
# 验证记录
|
||||
|
||||
日期:2026-10-23。环境:Windows 10/11 x64,开发机网卡 `以太网`(索引 16),IPv4 地址 `192.168.6.119/24`,主路由 `192.168.6.1`。
|
||||
日期:2026-10-23(初版)、2026-10-24(第二轮修复)。环境:Windows 10/11 x64,开发机网卡 `以太网`(索引 16),IPv4 地址 `192.168.6.119/24`,主路由 `192.168.6.1`。
|
||||
|
||||
## 已实际验证
|
||||
## 第七轮修复(2026-10-24,关闭登录自启必报错:按 HRESULT 而不是按异常类型判断)
|
||||
|
||||
症状:点「登录后自动启动」必弹「开机自启设置失败:找不到文件或目录:系统找不到指定的文件。(0x80070002) 已回滚到原设置。」,这个开关因此永远关不掉。
|
||||
|
||||
日志给出的因果链(01:20:55):`SetEnabled(false)` → `RemoveTask()` 删掉任务 → 读回状态 `QueryEnabled()` → 抛 `System.IO.FileNotFoundException 0x80070002` → 控制器记失败 → 托盘回滚成“已启用” → 界面报错、任务依旧在。
|
||||
|
||||
真因:晚绑定 COM(`dynamic`)抛出的异常类型是 CLR 按 HRESULT 翻译过的——任务不存在时拿到的是 `FileNotFoundException`,不是 `COMException`。原代码三处 `catch (COMException) when (HResult == 0x80070002)` 因此一次都没命中,「任务不存在」被当成真错误抛到了界面。本机实测确认:`GetFolder('\').GetTask('不存在的名字')` → `System.IO.FileNotFoundException / 0x80070002`。
|
||||
|
||||
修法:新增 `LoginStartup.IsTaskMissing(Exception)`,只认 HResult(0x80070002 / 0x80070003 / 0x8004130F),三处捕获统一改成 `catch (Exception) when (IsTaskMissing(e))`:删任务、读任务动作、读回状态。
|
||||
|
||||
本轮验证:
|
||||
|
||||
- 新增回归测试「missing-task detection follows HRESULT, not exception type」:0x80070002 / 0x80070003 / 0x8004130F 三种判定为“任务不存在”,0x80070005(拒绝访问)必须继续上报。本地测试总数 46 → 47,全绿;探测离线测试 16/16。
|
||||
- Release 构建 0 警告 0 错误(`TreatWarningsAsErrors` 开启);重新发布 `artifacts\publish\GatewayKeepalive.exe`(545,472 字节)。
|
||||
- **未验证**:真正的“关 → 开 → 关”需要提权点击,命令行侧没有管理员权限,只能由用户在托盘里点一次确认。
|
||||
|
||||
## 第六轮修复(2026-10-24,页签不重绘的真因、自启报错可诊断)
|
||||
|
||||
- **页签“同时激活”的真因**:`PanelChip.Selected` / `Emphasized` 是自动属性,赋值时既不 `Invalidate()` 也不 `UpdateStyles()`。切页只是把布尔值改了,像素还停在上一次:看上去两个页签都亮着,直到鼠标移进旧页签触发悬停重绘才“追上”状态——这正是“鼠标放上去才更新”。第五轮以为是配色太接近,只改了外观,所以问题照旧。现在两个属性改成显式字段 + 值变化时 `Invalidate()`,`SelectTab` 再对全部页签补一次重绘,切页即刻同步。
|
||||
- **自启报错为什么一直查不到**:`ToggleAutostart` 失败只弹提示、不写日志,日志里没有任何证据。现在四处留痕:控制器 `AutostartEnabled` / `SetAutostart` 记录成功与失败(含异常全文)、`ReportError` 把界面上每条错误写进日志、`SetEnabled` 入口记录「启用方向 + 是否管理员 + 进程路径」、启动时写一行 `启动自检:管理员=… 登录自启=… 进程=… 参数=…`。下次再报错,日志里直接就有原因。
|
||||
- **`--preview` 输出目录**:目标目录不存在时先创建,不再静默地什么都不生成。
|
||||
|
||||
本轮验证:
|
||||
|
||||
- Release 构建 0 警告 0 错误(`TreatWarningsAsErrors` 开启);核心测试 46/46、探测离线测试 16/16。
|
||||
- 重新发布 `artifacts\publish\GatewayKeepalive.exe`(545,472 字节)+ `diagnostics/`;`--preview` 指向一个不存在的目录也能正常产出 10 张图。
|
||||
- **未验证**:页签切换需要真人在新进程里点一次——离屏渲染每次整窗重绘,天生看不到“该重绘却没重绘”这类问题;自启报错的具体原因要等下一次点击后看日志。
|
||||
|
||||
## 第五轮修复(2026-10-24,页签单选语义、滚轮归属、自启状态读回)
|
||||
|
||||
- **页签“同时激活”**:选中态原来是半透明强调底 + 彩色描边、深色字没变,和悬停态(只换底色、文字变亮)几乎一样——鼠标一经过,就有两个页签看起来都是“当前页”。现在选中项是实心 Accent 底 + 深色字(和“保存”主按钮同一套),悬停只给一层很浅的中性底、永不用强调色,未选中项既无底色也无描边。单选语义靠形状和颜色一起表达,不靠深浅差别。**注意:配色不是真因,见第六轮。**
|
||||
- **鼠标停在输入框上页面滚不动**:滚轮让页逻辑原来只挂在多行框上,单行框(网关、DNS、端口、最少成功数)照样把滚轮吃掉。现在任何输入框“自己滚不动”时都把滚轮让给外层页面;页面本来不需要滚动时不吃事件,继续向上冒泡。
|
||||
- **自启状态读错**:任务计划 COM 偶尔读不到刚注册好的任务(同一台机器上 `schtasks` 却看得见),界面就把“已启用”显示成关闭,用户再保存一次又会重复注册任务。现在 COM 读不到时用 `schtasks /Query /TN … /XML` 复核;两条路都读不到才报错,并且同一个失败签名只往日志里写一次,不再每个检查周期刷屏。
|
||||
- **启动时的两次提示**:第一次是 exe 带“来自网络”标记时 Windows 的“未知发布者”安全警告——当前发布目录里的 exe 已无该标记(`Get-Item -Stream *` 只有 `:$DATA`),发布脚本产出的是本机新建文件,不会再带;第二次是 UAC,改路由和 DNS 必须提权,系统强制,程序无法绕过。
|
||||
|
||||
本轮验证:
|
||||
|
||||
- Release 构建 0 警告 0 错误(`TreatWarningsAsErrors` 开启)。
|
||||
- 核心测试 46/46、探测离线测试 16/16。本次运行环境不再限制文件写入,此前因沙箱拒绝 `File.Replace` 而失败的 24 项全部通过。
|
||||
- 重新发布 `artifacts\publish\GatewayKeepalive.exe`(544,960 字节)+ `diagnostics/`;`--preview` 出 10 张图,三页设置与托盘面板逐张核对,`docs/screenshots/` 已更新。
|
||||
- **未验证**:页签悬停态与输入框上的滚轮需要真人用鼠标确认(离屏渲染只能看到未悬停的画面)。
|
||||
|
||||
## 第四轮修复(2026-10-24,独立复查后加固)
|
||||
|
||||
一次只读代码复查(未看本轮改动过程)在本轮改动里找出了四处真实缺陷,已全部修掉:
|
||||
|
||||
- 保存失败会留下一个关不掉的窗口:`SettingsForm.SaveAsync` 出错时把 `_closing` 留在 `true`,之后的关闭按钮和 Esc 都不再响应。改为保存失败时复位标志;窗口/页面已释放时(保存回调可能在 `Dispose` 之后触发)所有 UI 更新都先判 `IsDisposed`。
|
||||
- 自绘画笔泄漏:`PanelChip` 选中态背景的 `SolidBrush` 没释放,每次重绘漏一个 GDI 句柄(面板常驻,长时间运行会累积)。
|
||||
- 滚轮“跳一格再拖会弹回”:`DarkScrollBar` 页跳后没有重置拖动基准,拖动被算成从跳转前的位置开始。页跳后同步基准值。
|
||||
- 多行输入框吞掉滚轮:文本框能滚就归它,滚不动时把滚轮让给外层页面(`FieldBox` → `DarkScrollPanel`,同一档步长),不会再出现“鼠标停在多行框上页面滚不动”。
|
||||
- 另外一并加固:`schtasks` 改为并发读取 stdout/stderr 后再等退出(顺序 `ReadToEnd` 会被写满的 stderr 缓冲卡住,20 秒超时形同虚设);读取任务动作时释放全部 COM RCW;`RepairIfStale` 除了比对路径也比对 `--minimized` 参数;校正任务改为后台线程执行,不再拖慢托盘点亮;页面栈在布局时清理已释放的项;`WrapTextLabel` 改用 `TextRenderer` 绘制并用 `TextRenderer.MeasureText` 量高(绘制与测量同一条路径)。
|
||||
|
||||
本轮验证:
|
||||
|
||||
- Release 构建 0 警告、0 错误(`TreatWarningsAsErrors` 开启)。
|
||||
- 新增回归测试「登录自启任务 XML」:断言动作命令、`--minimized`、工作目录、`InteractiveToken`、`HighestAvailable`、电源与超时设置、单实例策略。本地测试总数 45 → 46。
|
||||
- 探测离线测试 16/16;核心测试在本机沙箱内 22 通过 / 24 失败,失败项与第二轮完全相同(沙箱拒绝 `File.Replace` 等文件写入),新增用例通过。
|
||||
- 发布:单文件约 543 KB + `diagnostics/`;已发布 exe 的 `--preview` 输出 10 张界面图,只读诊断 `--diagnose` 返回 `readOnly=true`、网关 `192.168.6.1`、接口索引 16。
|
||||
- 界面:三页设置与托盘面板重新渲染后逐张核对,截图已更新到 `docs/screenshots/`。
|
||||
|
||||
## 第三轮修复(2026-10-24,界面细节与启动体验)
|
||||
|
||||
- 页签误判“同时激活”:`PanelChip` 未选中时改成既无底色也无描边(只有悬停/按下才浮现),选中项才有强调底色与强调色文字,单选语义在视觉上一眼可辨。
|
||||
- 设置窗口进入任务栏:改为非模态独立窗口(`Show()` + `ShowInTaskbar=true`),不再依附托盘浮窗;打开设置也不再让整个程序处于忙碌状态。
|
||||
- 启动提示与启动反馈:手动启动(非 `--minimized`)一定弹出面板,避免“以为没启动→反复双击→反复弹 UAC”;新增 `tools/unblock-publish.ps1` 清除发布产物的“来自网络”标记,README 说明两次系统提示的来源(未签名安全警告 + UAC 提权)。
|
||||
- 多行输入框与滚轮:`FieldBox` 只在内容真的放不下时才挂竖条;`DarkScrollPanel` 命中“有竖条的多行框”时把 `WM_MOUSEWHEEL` 转发给它,页面不需要滚动时不再吞掉滚轮事件。
|
||||
- 复查:Release 构建 0 警告 0 错误;预览重新渲染后逐张核对(三页页签、探测目标多行框不再出现残留方块)。
|
||||
|
||||
## 第二轮修复(2026-10-24)
|
||||
|
||||
针对反馈的五个问题:
|
||||
|
||||
- 设置界面重做:左侧分段标签改成自适应宽度的小页签,卡片圆角/内边距/行距统一,输入框改为自绘圆角容器(去掉系统浅色边框与原生滚动条),复选框改为自绘勾选行,标题区与底栏各有一条分隔线,窗口默认 720×620 并随 DPI 缩放。
|
||||
- 设置窗口位置:不再 `ShowDialog(owner)` 走 `CenterParent`(原先会被托盘浮窗挤到屏幕右下角并压在浮窗下面),改为按鼠标所在屏幕居中,`OnShown` 再校正一次。
|
||||
- 设置打开时可以退出:点“退出程序”会先请求设置窗口关闭,`OpenSettingsAsync` 结束后自动继续退出流程;保存进行中则等保存完成后再退出。
|
||||
- 登录自启重写:任务 XML 交 `schtasks /Create /XML` 创建,失败回退任务计划 COM;两条路径都在创建后读回 `Enabled` 校验,任一失败都会带上系统返回原文报错并写日志。启动时若发现任务指向的程序路径与当前可执行文件不一致,会自动改写(需管理员)。
|
||||
- 发布只看依赖运行时版:`build.ps1` 去掉 `-SelfContained`,只产出单文件 + `diagnostics/`,发布命令加 `-p:NuGetAudit=false`(本项目零外部包,离线也能发布)。
|
||||
|
||||
本轮验证:
|
||||
|
||||
- Release 构建 0 警告、0 错误。
|
||||
- 探测离线测试 16/16 通过。
|
||||
- 核心测试在本机沙箱内 21 通过 / 24 失败:失败项均为 `UnauthorizedAccessException`/`IOException` 发生在 `File.Replace` 等文件写入路径上,是当前受限运行环境拒绝文件替换导致,与本轮改动无关;在正常桌面会话下需复跑确认。
|
||||
- 发布:`artifacts/publish/GatewayKeepalive.exe`(约 524 KB 单文件)与 `diagnostics/GatewayKeepalive.Cli.exe` 均正常;托盘 exe `--preview` 输出 10 张界面图,只读诊断 `--help` 正常。
|
||||
- 界面:三页设置与托盘面板四态均重新渲染并逐张检查,截图已更新到 `docs/screenshots/`。
|
||||
|
||||
## 已实际验证(第一轮,2026-10-23)
|
||||
|
||||
- 解决方案 Debug/Release 构建:0 警告、0 错误(`TreatWarningsAsErrors` 已开启)。
|
||||
- 核心离线测试:45/45 通过,覆盖状态机计数与冷却、切换故障补偿、外部修改保护、跨网络恢复拒绝、无变化跳过写入、持久的暂停状态、Stop 恢复基线、写入前现场复核、原子 JSON 持久化与原生结构布局。
|
||||
@@ -10,7 +96,7 @@
|
||||
- 只读诊断:成功读取网卡、IPv4 默认路由、DNS 配置和有效选路;`NativeLayout` 为 104/168/64 字节,路由 metric 偏移 84,接口 metric 偏移 148,与 x64 SDK 结构一致。运行前后系统网关保持 `192.168.6.1`。
|
||||
- 切换计划预演(`--dry-run --proxy`):生成目标 metric 1 的旁路由默认路由,原主路由降为 metric 101,DNS 指向 `192.168.6.2`,未写系统。
|
||||
- 真实分层探测:主路由 TLS 出口 2/2 通过;`192.168.6.2` 的 TCP/DNS/SOCKS 探测超时,判为旁路由不可用,符合当前设备实际状态。
|
||||
- 发布包:依赖运行时单文件约 490 KB,独立单文件约 116 MB;两者托盘 `--preview` 均能启动并输出 10 张界面图,无异常输出;独立版只读诊断通过。
|
||||
- 发布包:单文件约 524 KB(依赖 .NET 10 桌面运行时);托盘 `--preview` 能启动并输出 10 张界面图,无异常输出;只读诊断通过。
|
||||
- 界面预览渲染:主面板正常/故障回退/未配置、诊断展开,设置三页及滚动后状态,均在 200% 缩放下检查过无裁切、无浅色系统控件。
|
||||
|
||||
## 未验证,需真机确认
|
||||
@@ -20,7 +106,7 @@
|
||||
- 实际切换到旁路由并读回路由/DNS/出口;旁路由断开后的自动回退;恢复后回切旁路由。
|
||||
- 旁路由 DNS 正常但默认转发失效、SOCKS5 正常但转发失效等不对称故障。
|
||||
- VPN 或其他网卡同时持有默认路由、DHCP 续租、拔插网线、休眠唤醒后的行为。
|
||||
- 登录后最高权限计划任务的创建、注销/重启后启动,以及程序移动路径后的重新设置。
|
||||
- 登录后最高权限计划任务的创建、注销/重启后启动(本轮重写后仍需要真机管理员账号确认一次)。
|
||||
- 崩溃后重启识别未完成切换日志的实际恢复流程。
|
||||
|
||||
首次在目标机启用前,建议先运行发布的 `diagnostics/GatewayKeepalive.Cli.exe --diagnose`,确认网卡 GUID、主/旁路由和 DNS 与计划一致,再用 `--dry-run` 核对计划,最后通过托盘界面保存并手动启用自动保活。
|
||||
|
||||
Reference in New Issue
Block a user