Skip to content

feat: 支持提醒正文时长与冷却下限配置,并修复热键确认链路 - #13

Open
Starsky16 wants to merge 25 commits into
Yeson38:masterfrom
Starsky16:pr/8-hotkey-confirm-fix
Open

Starsky16 wants to merge 25 commits into
Yeson38:masterfrom
Starsky16:pr/8-hotkey-confirm-fix

Conversation

@Starsky16

@Starsky16 Starsky16 commented Sep 27, 2026 •

Copy link
Copy Markdown

Summary

  • 修复热键确认链路:窗口内已确认的按键不再被超时判定丢弃;窗口超时未按键改为进入冷却。
  • 状态点改为组件自持"最近一次已应用值"比对,修复脉冲漏读导致的形状冻结。
  • 提醒正文显示时长改为在通知提供方配置;判定源冷却时间下限提升至 60 秒。
  • 清理死代码:删除运行时服务上已无读取方的 IndicatorStateChanged 及其失效注释。

变更

  • HotkeyConfirmDecisionSource:已确认分支前置于超时判定(避免落在窗口最后一个采样拍里的确认被当成超时清掉);窗口超时写入冷却;新增 CancelWindow(),供时段闸门作废遗留窗口且不写冷却。
  • HotkeyWindowMonitor:补日志——未解析到 KeyboardCapture 首次告警、接入成功打 Information、窗口内命中/未命中按键分级记录(未命中降为 Debug,避免打字污染宿主日志)。
  • DecibelComponent:状态点改为自持"最近一次已应用的形状 + 数字判据"做比对,最坏只滞后一个组件节拍,不再长期冻结。
  • 通知提供方新增 OverlayDurationSeconds(默认 30,面板与运行时均夹取 1..600),正文时长可配;一次提醒总时长 = 遮罩 5 秒 + 本值。
  • 两个判定源的冷却时间下限由 0 提升至 60 秒(设置页滑块与运行时夹取双保险),兜住存量配置里的小值。
  • 删除 AlertRuntimeService.IndicatorStateChanged 及其赋值:组件改为自持比对后它已无任何读取方,原注释"组件据此跳过无变化的视觉重设"与事实不符。IndicatorStateResolver 快照里的 StateChanged/Changed 是解析器纯逻辑契约、有独立单测覆盖,未在本次清理范围内。
  • README 与设置页文案同步。

验证

  • dotnet build -c Release:0 警告 0 错误。
  • dotnet test Tests/Decibel_Monitor.Tests -c Release:133 通过 / 0 失败。
  • deepsec shield scan Alarm --layer l1,l2:0 findings(DeepSec 不覆盖 axaml,相关界面改动已人工审查兜底)。
  • 实机:窗口内按键后下一拍发出提醒;窗口超时未按键进入冷却且三角不再长期常亮;课间遗留窗口在闸门解除后不补发提醒;调整提醒正文时长后生效;存量冷却小值被抬到 60 秒。

说明

本 PR 为堆叠系列的第 8 环(末环),其 tree 与 dev 完全一致;上游既有 PR 同样以 master 为 base,评审时请以 Commits 中属于本环的 4 个提交为准。

本 PR 由 AI 辅助生成。

- 新增 Services:AudioPeakMeter(门面)、AudioDeviceEnumerator(设备枚举与缓存)、ContinuousPeakCapture(常驻静默捕获流)、OneShotPeakCapture(一次性三级回退采样)、DecibelCalculator(线性峰值与显示刻度换算)、PeakSample(采样结果纯数据)。

- 新增插件全局设置 DecibelMonitorGlobalSettings 与持久化服务 DecibelMonitorSettingsService(整体反序列化,新增设置项无需同步维护)。

- Plugin.Initialize 注册全局设置服务与共享采样服务;组件将在后续 PR 切换为该服务。

- 新增 Tests/Decibel_Monitor.Tests(xunit),覆盖计算换算与采样数据纯逻辑,共 28 条用例全绿。

由 VS Code 中的 Cline 辅助生成。
- DecibelComponent 从宿主 DI 获取 AudioPeakMeter 与 DecibelMonitorSettingsService,改用 DecibelCalculator 做线性峰值到显示刻度的换算,不再自行枚举设备并三级回退采样。

- 数值改用 AvaloniaProperty(StyledProperty)绑定,移除手写的 INotifyPropertyChanged;采样 Tick 增加节流,避免异步任务堆积;放大倍数取自全局设置,同一麦克风所有组件数值一致。

- 组件设置控件精简为显示侧偏好(分贝前缀、更新频率);测量/校准与提醒设置分别移至插件设置页与通知提供方设置。

- Plugin 移除重复的 GetVoicePeakValue()。

- 编译警告由 3 个降为 0 个(原 CS0108/CS4014 随旧实现一并移除)。

由 VS Code 中的 Cline 辅助生成。
- 新增 DecibelNotificationProvider:继承 NotificationProviderBase<DecibelNotificationProviderSettings>
  以获得宿主自动持久化的通知侧设置,注册「超过分贝阈值」渠道并通过 Channel(...) 发送,与内置通知提供方保持一致。
- 提醒判定抽为纯函数 DecibelAlertEvaluator.Evaluate(...):严格大于阈值才触发、冷却期内不重复通知;
  配套 9 条边界单元测试(等于阈值不触发、冷却中不通知、冷却刚过可通知、冷却下限 1 分钟等),单元测试总数 37 条全绿。
- 新增通知侧设置控件:启用提醒、阈值(dB)、提醒文字、冷却时间(分钟)。
- 组件展示评估结果:超阈值时显示红色提示文字,可用组件设置里的「组件内提示」关闭(关闭后仍照常发送通知);
  提供方实例动态读取,避免组件先于 IHostedService 创建导致提醒永久失效。
- README 补充阈值提醒的使用方式、设置项与排查说明。

由 VS Code 中的 Cline 辅助生成。
- 新增插件设置页(设置 → Decibel_Monitor,External 分类):全局校准(参考 dB 0..150、放大倍数、校准按钮)、
  持续监听麦克风与采样参数、默认捕获设备信息与捕获设备列表诊断。
- 校准改为调用 AudioPeakMeter.CaptureSamplePeakAsync 显式采样一次,结果写入全局放大倍数,
  插件内所有组件立即生效(同一麦克风各组件数值一致)。
- Plugin 注册设置页;Decibel_Monitor.csproj 补充设置页 XAML 与代码后置的 DependentUpon。
- README 更新:设置分布(组件设置 / 通知设置 / 插件设置页三处)、校准位置由组件设置改为插件设置页、
  补充全局设置项与故障排查说明。

由 VS Code 中的 Cline 辅助生成。
- IntegrationTest.yml:下载官方 ClassIsland 2.1.0.1 folder 包,用 -epp 加载本插件并启动,
  轮询等待插件写入 loaded.marker 以确认 Initialize 完整执行(注册组件与提醒提供方无异常);
  超时或进程提前退出即失败,并在失败时打印 AppRoot 目录便于排查。
- Plugin:设置 DECIBEL_MONITOR_CI_MARKER=1 时向插件配置目录写入 loaded.marker,供上述工作流判定加载成功;
  未设置该环境变量时行为与原来完全一致。
- Build.yml / Test.yml:构建与单元测试步骤对齐现有工程(tools/publish.ps1、Tests/Decibel_Monitor.Tests)。

由 VS Code 中的 Cline 辅助生成。
组件由宿主从 DI 根容器以瞬态方式解析且从不调用释放方法,原实现在构造函数中启动 DispatcherTimer:已启动的定时器被 Dispatcher 强引用,Tick 闭包会永久钉住组件实例,导致组件重建后旧实例与定时器都无法回收。改为随视觉树附着/分离启停定时器,并移除不再需要的 IDisposable。

已知行为变化:组件不在视觉树上时不再采样,依赖 Tick 的阈值提醒在组件离树期间不会触发。
- 新增 AverageVolumeAlertEvaluator(滑动窗口平均判定,严格大于阈值)

- 新增判定源抽象 IAlertDecisionSource 与 AlertContext/AlertDecision 契约

- 新增判定源①AutoThresholdDecisionSource:超阈值且冷却已过即提醒

- 新增判定源②HotkeyConfirmDecisionSource:超阈值开筛选窗口,窗口内命中热键才提醒

- 新增仲裁模块 AlertDecisionCoordinator:自持优先级配置、统一归口发出通知事件

- 测试工程链接上述纯逻辑源文件,新增 43 条用例(共 80 条全绿)
- Alarm/HotkeyWindowMonitor:经 IAppHost 动态解析 KeyboardCapture 全局键盘服务
  (非必需依赖,未解析到则定时重试降级为空转),把筛选窗口期间的 KeyDown
  封送回 UI 线程并映射修饰键后转发给热键判定源,暴露窗口开关状态事件供组件红绿点。
- Alarm/AlertRuntimeService:周期采样构造判定上下文交由仲裁模块裁定,
  经 NotifyRequested 统一接入现有通知提供方发出强调通知(语音由宿主负责),
  执行提醒后进入 3 秒防自激暂停分贝采样判定并清空样本窗口。
- Plugin.cs:注册自动/热键两个判定源、仲裁模块及两个宿主服务(默认均未启用)。
- 通知提供方设置只保留提醒文字,阈值与冷却统一由判定源负责,消除双重门槛
- 插件设置页新增“提醒判定”分区:平均窗口时长、判定源优先级排序、自动提醒与热键确认设置
- 分贝组件改为纯显示侧,新增热键筛选窗口红/绿点指示,只读取提醒运行时服务状态
- 仲裁模块支持运行时应用判定源优先级顺序
- 组件不再显示提醒文字,统一为单一状态点:绿=正常,红=提醒触发中(含筛选窗口开启与冷却期)
- 判定源接口新增 Description 与 IsCoolingDown,供设置页展示与状态点判定
- 设置页补全判定源分组名(判定源①/②)、阈值与冷却/窗口时长滑块的数值显示、优先级列表说明
- 组件设置项 ShowAlertTextOnComponent 改为 ShowStatusIndicator(组件上不再有提醒文字)
- 窗口开启期间状态点在红绿之间交替(400ms 半周期),与静态红/绿区分开
- 窗口开启时组件更新节拍收紧到闪烁半周期,避免更新频率设置高时看不出闪动
- 组件设置说明与设置页描述同步补充闪动含义
把散落在运行时服务(布尔 OR 压平)与组件(自行解释布尔)两处的提醒状态判断
收敛为一个枚举与一个纯函数式合并器,为后续「形状 + 填充 + 呼吸 + 数字颜色」
四通道显示改造提供唯一判定依据。

- 合并规则按「是否需要用户动手」排序:窗口 > 超阈值 > 冷却 > 正常 > 无数据,
  窗口压制冷却,避免两源同时启用时「该按键」被长冷却盖掉
- 阈值迟滞(施密特触发):只作用于数字颜色判据,迟滞带 2.0 显示刻度单位
  (1 单位 = 1 dB,等价 20*log10(0.8) 约 -1.9 dB),不影响判定源的提醒行为
- 时间滞回:升级立即生效、降级需新态持续 1 秒,计时用真实经过时间而非采样次数
- 冷却期内数字判据改用 1 秒短窗口平均;输出 Changed 标志与显式 Reset()
- 新增 32 条单测覆盖合并组合、优先级压制、两种滞回、判据切换与呼吸时机
用 IndicatorStateResolver 替换运行时服务里布尔 OR 拼装的 IsAlertStateActive,
状态含义与滞回规则统一归解析器,服务只负责压平各源状态与维护两类窗口。

- 暴露 IndicatorState / IndicatorValueState / IsIndicatorBreathing / IndicatorStateChanged
  供组件读取,不再暴露含义被压平的布尔量
- 新增 1 秒短窗口平均(独立于判定用的平均窗口),按真实时间戳剔除过期样本,
  供冷却期数字判据使用
- 数字判据阈值取已启用判定源中的最小者(各源共用同一数值)
- HotkeyConfirmDecisionSource 新增 WindowOpenUntilUtc,供计算窗口剩余时间与呼吸时机
- 默认捕获设备变化(含被拔出/禁用后为 null)时 Reset 解析器与短窗口,避免残留红色;
  无可用捕获设备时视为无采样,状态切 NoData、数值显示 N/A,不再伪装成绿色正常
- 组件仅为保持编译通过做最小适配,视觉行为与改动前一致(视觉改造在下一阶段)
状态点由"单点红绿闪动"改为四通道编码,不再用红绿交替造成最紧急状态有一半时间
显示成正常:

- 形状 12px 互斥显示:常态低不透明度绿圆 / 超标红实心方 / 冷却期红空心方 /
  待热键确认橙三角;无采样或未启用判定源时不显示任何形状
- 呼吸 Animation 驱动三角不透明度,触发点为窗口剩余时间 <= min(3s, WindowTimeout/4),
  窗口开启期间静置
- 数字 Foreground 绑定判据颜色(冷却期用 1 秒短窗口平均),改用宿主主题语义画刷
  SystemFillColorCriticalBrush / SystemFillColorSuccessBrush / SystemFillColorCautionBrush
- 删除 TickCount64 取模闪烁与窗口期 400ms 强制刷新;保留视觉树生命周期管理
- 形状与颜色只在 Changed 为真时重设,并用 _visualsApplied 在首次附着时强制应用一次
  (状态未变化时 Changed 为假,否则新实例不会显示形状),分离时复位
状态点由单点红绿闪动改为四形状体系后,README 与设置页文案仍停留在旧描述:

- README 新增「提醒状态点」小节:形状对照表(半透明绿圆=正常 / 红实心方=正在
  超阈值 / 红空心方=冷却期 / 橙三角=待热键确认且窗口即将超时时呼吸)与数字颜色
  判据说明(非冷却期用滑动平均值、冷却期用 1 秒短窗口平均、2 dB 迟滞、无采样不设色)
- 设置页「判定源②」与「筛选窗口时长」两处 Description 由红绿闪动改为橙色三角与呼吸提示
- 组件设置页说明与 ShowStatusIndicator 的 XML 注释同步为四形状语义
提醒判定迁到插件设置页的"判定源"、通知提供方改为只负责提醒正文之后,文档与
注释仍指向旧位置与已删除的设置项:

- README:`ShowAlertTextOnComponent` 改为 `ShowStatusIndicator`;通知提供方设置只
  剩 `AlertText`(删去 IsAlertEnabled / AlertThreshold / AlertCooldownMinutes);
  "设置位置一览"与"阈值提醒"小节改为指向插件设置页的判定源;补全判定源①/②字段、
  平均窗口与优先级;故障排查补上"判定源默认关闭"与判定源②需 KeyboardCapture
- 组件设置页 XAML 注释:提醒相关设置的指向同步为"判定源在插件设置页、正文与语音在
  通知提供方"
实机验证发现冷却期形状与设计 3.2 节不符:判定源的 IsTriggerActive 不受冷却影响,
低阈值用法下 600 秒冷却期内几乎全程显示实心方,"刚提醒过"这一事实在形状上丢失,
且数字颜色的"冷却期改用 1 秒短窗口平均"分支走不到。

改为按判定源分别计算"超阈值且未冷却"后再取或,使冷却期形状恒为空心方、
数字判据切换生效。同步 README 形状对照表与设置页文案,并修正 README 中
"未启用任何判定源时数字显示 N/A"的不准确表述。

副作用:提醒与冷却在同一拍写入,因此 Alerting(红实心方)在冷却时间非 0 时
实际不可见,仅在冷却时间为 0 时出现;已在 README、组件设置页与 resolver 注释中
按此精确说明。
课间休息与每节课开头若干分钟内的超阈值不再发出通知,避免课间交谈、上课起立
等声音被当成吵闹造成误报。

关键点:抑制时直接跳过仲裁模块的 Decide 调用,而不是只在通知出口拦截。
Decide 一旦被调用就会写入冷却时间,课间的一次误报会吃掉整段冷却,导致进入
上课后该提醒而不提醒,反而比不抑制更严重。抑制期间状态点改由"当前窗口平均
是否超阈值"直接驱动,仍照常显示噪音(超阈值红实心方、否则绿圆)。

- 新增 Alerting/ScheduleGate.cs:纯逻辑时段闸门,上课起点取 OnClass 上升沿并用
  真实时间计时;课表未启用或未加载时整体旁路。自定义 SchedulePhase 枚举以避免
  测试项目引用宿主类型,新增 14 条单测
- AlertRuntimeService 惰性解析 IAppHost.TryGetService<ILessonsService>()(零新增依赖)
- 新增设置项 SuppressAlertDuringBreak(默认开)与 ClassStartProtectionMinutes
  (默认 3,0 表示关闭),设置页新增「时段闸门」分组
IndicatorStateChanged 是宽度仅一个采样拍(200 ms)的脉冲,组件节拍(默认 500 ms)
可能整拍落在脉冲之外;一旦漏读,形状会一直冻结在旧值(表现为热键筛选窗口已开却
仍显示空心方,而此时按键其实仍能提醒)。改为由组件自持最近一次已应用的状态与数字
判据做比对,最坏只滞后一个组件节拍,不会长期冻结。
- 消费已确认按键的分支提到超时判定之前:HandleKeyPress 接受按键时已校验未超时,
  若先判超时,落在窗口最后一个采样拍里的确认会被当成"超时未确认"清掉,用户按了键
  却既不提醒又白进一次冷却。
- 窗口超时未按键同样写入冷却(视为"已经给过一次机会"),否则下一拍立即重新开窗,
  低阈值用法下三角会长期常亮且永远看不到冷却态。
- 新增 CancelWindow(),供时段闸门抑制期间作废遗留窗口(含其中已命中的确认),
  不写冷却;否则会出现"课间按的键,上课后才响"。
- HotkeyWindowMonitor 补日志:未解析到 KeyboardCapture 服务首次告警,接入成功打
  Information,窗口期内未命中的按键降为 Debug(避免打字污染日志),命中仍打
  Information。该链路此前全程静默,是判断"断在哪一环"的唯一依据。
- 通知提供方新增 OverlayDurationSeconds(默认 30,面板与运行时均夹取 1..600),
  用于设置提醒正文(第二段)的显示时长;遮罩沿用宿主默认 5 秒,因此一次提醒的总
  时长 = 5 秒 + 本值。此前正文时长硬编码,缩短提醒只能改代码重新发布。
- 两个判定源的冷却时间下限由 0 提升至 60 秒:设置页滑块与运行时夹取双保险,兜住
  存量配置里的小值。一次提醒本身会盖住状态点约 35 秒,比它更短的冷却期会被整段
  遮住,冷却态根本看不见。
- README 同步:判定源②超时进冷却、状态点对照表两行、通知提供方新增设置项。
组件改为自持"最近一次已应用值"比对后,运行时服务上的 IndicatorStateChanged 不再有
任何读取方,其注释"组件据此跳过无变化的视觉重设"也与事实不符,留着会误导后续维护。
删掉属性、其赋值,以及组件注释里对该属性的指名引用(该注释保留,它是自持比对的原因说明)。

不涉及 IndicatorStateResolver 快照里的 StateChanged/Changed:那是解析器纯逻辑模块的
输出契约,有独立单测覆盖,不在本次清理范围。
Starsky16 added a commit to Starsky16/Decibel_Monitor that referenced this pull request Oct 2, 2026
如实写明右侧改动曾以 PR Yeson38#6~Yeson38#13 提交上游、上游自 2026-02 起停止维护
且均未合并,现改为后续改动不再向上游提 PR。
Starsky16 added a commit to Starsky16/Decibel_Monitor that referenced this pull request Oct 2, 2026
默认分支 README 仍是旧口径(「这些改动只保留在本 fork,不向上游提交 PR」),
与实际情况不符:这些改动曾以 PR Yeson38#6~Yeson38#13 提交上游,上游自 2026-02 起停止维护、未合并。
本提交只改 README(不发版),包内说明仍以已发布的 0.2.0.1 为准。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant