Windows 网页壁纸:设计与原型改动记录
更新日期:2026-10-18。原型记录保留历史测试范围,正式实现状态见第 6 节。
当前状态:用户实际测试后确认“原型功能基本没什么问题”。原型已完成桌面挂载、鼠标移动和左键交互、波纹效果及性能观测;正式组件包、部署持久化和组件卡片已接入。该反馈代表当前机器上的基本体验认可,不代表所有 Windows 版本、DPI、显示器变化和异常恢复场景都已验收。
1. 最终需求
展示与内容
- 使用 Electron,仅支持 Windows。
- 首版只在系统主屏显示一张网页壁纸,覆盖主屏完整范围,位于桌面图标和现有组件下方,任务栏保持正常。
- 其他屏幕保持原样,不提供多屏配置。主屏改变时迁移并适配新尺寸,尽量保留页面状态。
- 正式内容来自已有组件包中的专用
WallpaperWidget页面;该页面不用于普通桌面组件、悬浮组件或后台任务。
输入规则
| 输入 | 壁纸行为 | 系统行为 |
|---|---|---|
| 鼠标移动 | 接收副本,用于波纹等效果 | 始终放行,不能限制光标移动 |
| 左键点击 | 接收按下/抬起副本,产生普通点击 | 始终放行,保留桌面自身行为 |
| 右键、中键、侧键、滚轮 | 不转发给壁纸 | 交由系统或原有窗口处理 |
| 键盘、快捷键、中文输入 | 不传入壁纸,壁纸不获取键盘焦点 | 保持系统原有输入归属 |
图标和现有组件优先。无需声明交互区域、添加元素标记或区分按钮与输入框;网页内部的左键命中由 Chromium 处理。原型额外屏蔽页面的双击、辅助点击、拖放和键盘相关 DOM 事件。左键按下/抬起是生成单击所需的底层消息,不扩展为完整拖放能力。
输入转发采用“复制并放行”,而不是独占输入。因此桌面取消图标选择、空白处框选等行为仍可能与网页左键交互同时发生。识别到系统菜单时不复制此次点击;菜单未被识别时,关闭菜单的点击也可能产生网页点击或波纹,但不能因此阻止系统收到原始点击。
运行、关闭与失败
- 宿主不主动暂停壁纸,声音和暂停由壁纸程序自行控制;宿主提供状态通知供页面决策。
- 未主动关闭时,正常退出应用后,下次启动恢复上次选择;不新增开机自启动功能。
- 用户主动关闭、首次加载失败或运行中恢复次数耗尽后,取消启用状态,下次启动不重试,等待用户手动重新设置。
- 替换时旧网页壁纸退出。新壁纸加载失败后通知用户并露出系统壁纸,不回滚到旧网页壁纸。
- 专用页面主动报告准备完成。正式部署需同时满足页面 ready 与原生挂载成功,才能显示为“已部署”。
- 首次部署和每次异常恢复的准备超时均为 30 秒。运行中异常最多恢复 3 次,恢复后稳定运行 60 秒才清零失败计数;耗尽后通知并停止。
上述完整生命周期是正式功能要求,当前原型只实现了其中的基础准备、挂载、停止和清理,具体差异见第 6 节。
2. 原型实际实现
原型独立放在 packages/electron/prototypes/web-wallpaper/,没有改动现有组件部署链。
模块与文件
| 文件 | 职责 |
|---|---|
packages/electron/prototypes/web-wallpaper/main.cjs | Electron 壁纸窗口、控制面板、ready 握手、屏幕事件、桥接通信、日志、冒烟检查与退出清理 |
packages/electron/prototypes/web-wallpaper/DesktopBridge.cs | Win32 桌面宿主探测与挂载、图标矩形缓存、鼠标 hook、消息副本投递、菜单状态检查 |
packages/electron/prototypes/web-wallpaper/preload.cjs | 最小 IPC 接口及壁纸页面输入过滤;控制面板保留正常输入 |
packages/electron/prototypes/web-wallpaper/wallpaper.html、packages/electron/prototypes/web-wallpaper/wallpaper.js | 动态测试页、点击计数、输入屏蔽测试控件、鼠标波纹及实时性能显示 |
packages/electron/prototypes/web-wallpaper/panel.html、packages/electron/prototypes/web-wallpaper/panel.js | 挂载、停止、截图、退出、诊断事件和人工验收勾选 |
packages/electron/prototypes/web-wallpaper/start.ps1 | 使用本机 .NET Framework 编译 x64 桥接程序并启动项目现有 Electron |
| package.json、.gitignore | 独立原型入口;忽略生成的 build 和 artifacts |
桥接是临时的独立 C# 程序,通过标准输入/输出与 Electron 交换 JSON。它调用 Win32 API,不依赖 Node 插件 ABI,也没有为此新增 npm 依赖。正式版本是否迁入已有 @widget-js/native 模块,属于后续接入工作。
桌面挂载
- Electron 创建覆盖主屏
bounds的无边框窗口。设置thickFrame: false,避免残留边框导致网页视口小于屏幕。 - 桥接探测 Progman、SHELLDLL_DefView 和壁纸 WorkerW;对窗口进行
SetParent及相应样式切换,并转换宿主坐标。 - 找到
Chrome_RenderWidgetHostHWND作为鼠标消息副本的目标。 - 页面报告 ready 后才允许手动挂载;挂载成功后以不激活的方式显示。
- 停止或正常退出时解除 hook、恢复窗口父级和样式、隐藏壁纸。未调用系统壁纸设置接口,原系统壁纸自然露出。
主屏变化时原型调整同一个窗口的尺寸和位置,不主动重载页面。Explorer 宿主失效时原型停止挂载,目前不负责自动恢复。
鼠标事件链路
flowchart TD
A[真实鼠标输入] --> B[Win32 WH_MOUSE_LL hook]
B --> C[CallNextHookEx:所有原始事件放行]
C --> D[Windows 桌面与系统菜单]
B --> E{移动或左键,且允许复制?}
E -->|是| F[PostMessage 到 Chromium 输入窗口]
F --> G[网页鼠标事件与波纹]
E -->|否| H[不向网页发送副本]复制前检查主屏范围、鼠标所在原生窗口和图标矩形。图标矩形由独立 UI Automation 线程每 250ms 更新;缓存超过 2 秒或查询失败时不向网页转发,系统输入继续放行。普通窗口和已有组件覆盖的位置也不按桌面空白处理。
壁纸窗口设置 focusable: false、focusOnNavigation: false 和 setIgnoreMouseEvents(true),避免直接获取输入;允许的鼠标事件从桥接副本进入。键盘通过 Electron before-input-event 与 preload 的 DOM 过滤进一步屏蔽。没有全局键盘 hook,没有键盘字符模拟。
GetGUIThreadInfo 只用于尽量避免将系统菜单操作复制给网页,不能作为是否放行系统事件的依据。最终输入策略在启动日志中标识为 copy-and-pass-through-v2。
3. 本次迭代中的关键修正
| 问题或原方案 | 最终处理 |
|---|---|
| 按元素标记交互区域,区分按钮与输入框 | 取消标记、矩形上报及控件分类;仅按允许的鼠标事件类型转发 |
| 要求键盘与中文输入 | 根据用户调整取消,窗口不可获取键盘焦点 |
| 仅排除右键,其他输入默认处理 | 收紧为鼠标移动和左键,其他鼠标及键盘输入全部排除 |
| 光标像被挡在某个区域外 | hook 曾在转发移动后返回非零,修正为始终放行原始移动事件 |
| 系统右键菜单无法通过左键关闭 | 仅判断菜单状态未覆盖本机菜单;最终取消左键拦截,所有原始事件都交给 Windows |
| 原型进程运行但面板未显示 | 在控制面板 ready-to-show 时显式显示并聚焦 |
| 网页视口小于主屏 | 去除原生残留边框,设置 Electron thickFrame: false;验证视口达到 2560×1440 |
| 难以判断鼠标是否跟手 | 增加移动波纹、点击涟漪和实时性能面板 |
最终保留输入副本,不拦截系统原始输入,是本轮实测后确定的原型行为。
4. 波纹与验证结果
移动鼠标产生青绿色波纹,左键点击产生较大的金色涟漪。每帧只消费最新鼠标位置,移动波纹生成间隔至少 24ms,活动波纹硬上限 64;过期对象会移除,避免高回报率鼠标造成事件绘制队列积压。
右下角展示最近最多 120 帧的平均 FPS、P95 帧间隔、超过 33.34ms 的慢帧数、绘制指令提交耗时和活动波纹数量。绘制提交耗时不包含完整 GPU 工作,帧间隔也不等于端到端鼠标延迟。
自动检查
最近一次记录:2026-10-01 00:06(Asia/Shanghai),Electron 43.1.1,主屏 2560×1440、100% DPI、60Hz。
| 检查 | 结果 |
|---|---|
| 桥接编译、JavaScript 语法 | 通过 |
| 页面 ready、WorkerW 挂载、Chromium 输入窗口发现 | 通过 |
| 网页实际视口 | 2560×1440 |
| 图标矩形读取 | 本机读取 25 个图标 |
| 输入策略模拟检查 | 15 类禁止事件未到达目标监听器;允许的移动和左键事件通过 |
| 壁纸窗口可聚焦状态 | false |
| 3 秒模拟派发 3000 次 DOM mousemove | 末次采样 59.9 FPS,P95 16.8ms,最近 120 帧慢帧 0 |
| 绘制指令提交均值、波纹峰值 | 0.42ms,24 / 64 |
| 正常退出 | 收到 cleanup-complete |
这些自动输入是页面内的合成事件,不经过真实系统鼠标 hook;自动检查不能替代原生菜单、层级和真实输入测试。性能数据是单机短时采样,不能推导出所有机器上都不卡顿。
人工反馈与剩余边界
用户在最终“复制并放行”版本上反馈原型基本无问题,作为本机基本交互体验的认可记录。未将这条反馈扩展为每一项专项验收通过。
正式接入前仍需检查 Win+D、不同 Windows 桌面实现、混合 DPI、分辨率与主屏变更、Explorer 重启、长时间运行和高负载。图标缓存存在 250ms 更新窗口;菜单识别仍可能漏判,此时关闭菜单与网页点击可能同时发生。PostMessage 输入副本的 Chromium 兼容性及鼠标捕获行为也需继续观察。
5. 运行与诊断
在仓库根目录运行,重新编译前先关闭正在运行的原型:
# 打开原型控制面板
& ./packages/electron/prototypes/web-wallpaper/start.ps1
# 仅编译原生桥接
& ./packages/electron/prototypes/web-wallpaper/start.ps1 -BuildOnly
# 自动挂载、性能采样、输入策略检查、截图并退出
& ./packages/electron/prototypes/web-wallpaper/start.ps1 -Smoke点击“挂载到桌面”开始;“停止并恢复桌面”卸载;关闭面板或按 Ctrl+Alt+Shift+Q 退出。该快捷键属于控制程序,不是壁纸接收键盘输入。
artifacts/session.jsonl 保存当前会话,artifacts/smoke.jsonl 保存最近一次冒烟检查,分别在相应模式启动时覆盖;需要保留时先复制。截图保存在同目录,页面截图只能证明网页渲染,不能证明桌面层级。生成的二进制、运行日志、截图及测试 profile 不作为源代码提交。
6. 正式功能实现(2026-10-01)
正式代码已接入;原型保留用于输入与 Windows 桌面兼容性诊断。正式窗口复用原型验证过的“复制并放行”输入策略。
| 接入项 | 目标 | 当前状态 |
|---|---|---|
| 核心定义 | 专用 WallpaperWidget 与独立 DeployMode.WALLPAPER,不要求网格宽高 | 已实现;宽高 0 仅为模型存储占位,实际采用主屏 bounds |
| 壁纸窗口与管理器 | 独立 WallpaperWindow / WallpaperManager,全局单实例、串行替换,经 WidgetManager 分派 | 已接入;取消代次阻止旧任务覆盖新选择 |
| 组件包加载 | 使用包入口和专用壁纸路径 | 已接入;支持按 packageJsonUrl 安装,包卸载关闭,包更新重载 |
| 卡片按钮 | 未部署显示“壁纸”,成功后显示“关闭”,可配置时显示“设置”,运行时提供 DevTools;禁用重复点击,跨卡片同步 | 已接入;状态 revision 防止较旧查询覆盖较新通知 |
| 就绪协议 | WallpaperApi.ready();页面与原生挂载共同决定成功;30 秒超时取消 | 已实现;每次恢复重新计时,初次失败不自动重试 |
| 持久化与启动恢复 | 正常退出保留选择;主动关闭和失败终态清除恢复意图 | 已接入 key_value 的 wallpaper.active.v1,不创建普通网格部署记录 |
| 异常恢复 | 最多 3 次;稳定 60 秒清零;失败后停止并通知 | 已实现;覆盖 renderer、桥接进程与桌面宿主丢失 |
| 状态通知 | 壁纸自行决定暂停和声音 | 已接入锁屏、解锁、挂起、恢复、显示器电源与前台全屏覆盖状态 |
| 主屏迁移 | 适配新主屏且尽量保留页面状态 | 调整同一窗口;混合 DPI、Explorer 与显示器专项验收仍待进行 |
主要代码落点:
packages/@widget-js/core/src/model/Widget.ts:专用模型和能力判断,同时同步 Electron 数据库侧模型。packages/electron/src/manager/WidgetManager.ts:分派新的部署模式,接入全局壁纸状态。packages/electron/src/manager/WallpaperManager.ts:独立负责生命周期、持久化、恢复和显示器事件;壁纸不参加普通组件的置顶逻辑。packages/electron/src/browser-window/widget/WallpaperWindow.ts:壁纸窗口、30 秒握手与原生桥接进程管理。packages/@widget-js/core/src/api/WallpaperApi.ts:壁纸专用 IPC 与状态订阅。packages/electron/native/wallpaper/WallpaperBridge.cs:WorkerW 挂载、输入副本、图标命中与显示环境状态。packages/electron/src/browser-window/widget/DesktopWidgetWindow.ts:保留普通组件职责,壁纸不继承网格吸附、编辑模式和 workArea 恢复逻辑。packages/widgets/src/pages/add/components/search-item.tsx:专用卡片按钮及状态订阅,关闭时携带 widgetName,旧卡片不能关闭其他壁纸。
兼容约束:BACKGROUND 仍表示无界面后台任务;旧 HostedMode.WALLPAPER 数值与现有 DeployMode.TRAY 冲突,不能直接复用。数据库存在部署记录不等于窗口运行成功,正式卡片状态应来自壁纸生命周期状态。
构建与检查
先构建本地 core,再构建 widgets 和 Electron。widgets 的 Vite 与类型配置均使用仓库内 core。Electron 的 Vite 构建会编译 C# 桥接并复制专用 preload;打包时 assets/wallpaper/** 放入 asarUnpack,终端用户无需现场编译。构建机需要 Windows .NET Framework C# 编译器及 UIAutomation 引用程序集。
已验证 core 的 ESM/CJS/声明构建、widgets 类型检查及生产构建、Electron 构建、原生桥接编译。生命周期与窗口测试覆盖替换、旧卡片关闭、加载中取消、初次失败、三次恢复上限、60 秒稳定期、启动恢复、ready 发送者、握手与 30 秒超时。正式集成尚未进行完整真实桌面人工回归;原型的测试结论不自动等同于正式包验收。
Electron 全仓类型检查仍有已有错误:ClipboardManager 的 native 声明、WindowManager 的 WindowInfo、部分 MCP 工具模型类型和旧桌面恢复测试的 protected 访问;本轮新增壁纸文件未报告类型错误。
WallpaperApi 接入
完整接入示例见 WallpaperApi。壁纸页面仅暴露受限的 WallpaperApi 查询、ready、openConfigPage 与订阅接口;管理界面使用 deploy/close。普通 DeployedWidgetApi 列表不包含壁纸,调用方通过 WallpaperApi.getState() 查询当前壁纸。