news 2026/10/3 13:48:18

Sunshine 增强版 Zako 虚拟鼠标驱动:UMDF 2.x HID 迷你驱动打包、安装与客户端实现解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Sunshine 增强版 Zako 虚拟鼠标驱动:UMDF 2.x HID 迷你驱动打包、安装与客户端实现解析
  • 音视频

【免费下载链接】foundation-sunshine

Sunshine fork: an enhanced sunshine, a self-hosted game streaming host for Moonlight with HDR10/HDR Vivid, virtual displays, advanced audio, optimized encoders, and a modern control panel.

项目地址:https://gitcode.com/gh_mirrors/sunshine5/foundation-sunshine
点击查看免费下载

导读

本文以 src_assets/windows/misc/vmouse/driver/README.md 为核心,系统讲解 Sunshine 增强版(foundation-sunshine)自带的 Zako 虚拟鼠标(Virtual Mouse)驱动的打包目录结构、WDK 构建产物要求、Windows 下的安装与卸载流程,并结合仓库源码剖析客户端如何通过 HID 输出报告与该驱动通信。读完本文,你将掌握该驱动从“构建产物整理”到“脚本安装/卸载”“源码级调用验证”的完整链路,可在实际部署或二次开发时直接复用。

驱动目录的定位与打包结构

在 Sunshine 增强版中,src_assets/windows/misc/vmouse/目录承载了虚拟鼠标功能的全部发布资产:

  • driver/:存放构建完成的驱动文件(即本文关联文档所在的目录,专门用于打包);
  • install-vmouse.bat:驱动安装脚本;
  • uninstall-vmouse.bat:驱动卸载脚本。

driver/README.md明确指出:该目录存放用于打包的已构建虚拟鼠标驱动文件,而不是驱动源码工程本身。驱动源码位于独立的 WDK 工程(drivers/virtual_mouse/),需要先在 Visual Studio 中构建,再把输出产物复制到此目录。换言之,driver/是“发布物暂存区”,安装脚本会从这里读取 INF、CER 等文件完成设备注册。

打包必需的驱动文件清单

按照 driver/README.md 的约定,从 WDK 构建输出中需要收集以下四个文件:

文件类型说明
ZakoVirtualMouse.dllUMDF 2.x HID 迷你驱动驱动主体,负责将 HID 输出报告转换为鼠标输入
ZakoVirtualMouse.inf驱动安装信息描述驱动安装参数、硬件 ID、设备类 GUID 等
ZakoVirtualMouse.cer测试签名证书用于将驱动证书导入 Trusted Root / Trusted Publisher 证书库
ZakoVirtualMouse.cat驱动目录(可选)生产签名场景下使用;测试/开发阶段可不打包

其中ZakoVirtualMouse.cat标注为可选:测试签名安装时由install-vmouse.bat直接通过certutil安装.cer证书,而生产环境需要正式的 WHQL/EV 签名目录文件。

使用 WDK 构建驱动的标准流程

原文档给出的构建步骤非常简洁,展开后是完整的四步:

  1. 在 Visual Studio 中打开驱动源码工程drivers/virtual_mouse/ZakoVirtualMouse.sln;
  2. 将解决方案配置切换为Release,平台选择x64(UMDF 驱动必须面向 64 位系统);
  3. 构建(Build)整个解决方案;
  4. 将构建输出目录drivers/virtual_mouse/x64/Release/中的.dll、.inf、.cer(以及生产场景的.cat)复制到 src_assets/windows/misc/vmouse/driver/。

关键前提:INF 必须经过 stampinf 盖章

构建后的 INF 若仍包含字面量占位符$UMDFVERSION$,将无法直接安装。安装脚本 install-vmouse.bat 中专门有一道“印章校验”:

findstr /C:"$UMDFVERSION$" "%DRIVER_DIR%\ZakoVirtualMouse.inf" >nul 2>&1 if not errorlevel 1 ( echo ERROR: Bundled ZakoVirtualMouse.inf is not stamped ^(UmdfLibraryVersion=$UMDFVERSION$^). echo This package will fail to install with error 87. ... exit /b 87 )

一旦发现未盖章的 INF,脚本会直接以错误码 87(ERROR_INVALID_PARAMETER)退出。脚本注释解释了根因:WUDF 协同安装器(coinstaller)在DIF_INSTALLDEVICE阶段遇到未替换的$UMDFVERSION$会报错 87,并遗留一个孤儿ROOT\HIDCLASS节点和损坏的 DriverStore 条目。因此构建完成后必须用 stampinf 工具(WDK 自带)替换 UmdfLibraryVersion 占位符,或将已盖章的驱动包放入driver/目录。

安装脚本全流程解析

install-vmouse.bat 是驱动落地的核心脚本,其执行流程如下。

1. 定位驱动文件与工具链

脚本以自身所在目录为基准定位:

  • DRIVER_DIR优先指向%~dp0driver(即脚本旁的driver\子目录);若该目录下没有ZakoVirtualMouse.inf,则回退到脚本所在目录(发布压缩包可能把驱动文件与脚本平铺);
  • Sunshine 根目录通过%~dp0..\..推导;
  • 驱动安装工具nefconw.exe期望位于tools\nefconw.exe,若不存在则回退到tools\vdd\nefconw.exe。找不到工具时脚本报错退出。

nefconw.exe承担了创建设备节点、卸载旧驱动、安装驱动等底层 PnP 操作,与虚拟显示器(VDD)组件共用同一套工具。

2. 脏环境判定与增量清理

set "VMOUSE_CLEANUP_REQUIRED=0" if exist "%DIST_DIR%\ZakoVirtualMouse.inf" set "VMOUSE_CLEANUP_REQUIRED=1" call :count_vmouse_devices EXISTING_VIRTUAL_MOUSE_DEVICES if !EXISTING_VIRTUAL_MOUSE_DEVICES! GTR 0 set "VMOUSE_CLEANUP_REQUIRED=1"

只有存在旧驱动包(tools\vmouse目标目录中已有 INF)或系统中仍存在虚拟鼠标设备节点时,才执行昂贵的 PnP / DriverStore 清理;全新安装则直接跳过,节省安装时间。设备计数通过 PowerShell 枚举ROOT\HIDCLASS\*并匹配硬件 IDRoot\ZakoVirtualMouse得到。

3. 停止 Sunshine 服务

为释放 HID 设备句柄,脚本在清理前停止服务。实现上刻意避开net stop(其可能因停止处理器卡住而阻塞最多 30 秒),改用:

sc query SunshineService | find /I "RUNNING" >nul 2>&1 if not errorlevel 1 ( set "SERVICE_WAS_RUNNING=1" sc stop SunshineService >nul 2>&1 ) taskkill /f /im sunshinesvc.exe >nul 2>&1

先sc stop优雅停止,再taskkill /f兜底强制结束sunshinesvc.exe,确保句柄立即释放;SERVICE_WAS_RUNNING标志用于安装完成后恢复服务。

4. 移除旧设备节点与驱动包

  • nefcon 主循环:循环调用nefconw.exe --remove-device-node --hardware-id Root\ZakoVirtualMouse --class-guid 745a17a0-74d3-11d0-b6fe-00a0c90f57da,每次循环后重新统计剩余设备数,直到归零或达到 20 次上限。脚本注释特别说明:不能只依赖 nefcon 的退出码,因为部分构建“打印删除错误却仍返回成功”,必须以实际 PnP 设备数为准驱动循环。
  • pnputil 兜底:对 nefcon 未能移除的残留实例,用 PowerShell 枚举ROOT\HIDCLASS\*中硬件 ID 为Root\ZakoVirtualMouse的设备,逐个执行pnputil /remove-device。
  • 卸载旧驱动:nefconw.exe --uninstall-driver --inf-path ...。
  • 清理 DriverStore:用 PowerShell 扫描%SystemRoot%\INF\oem*.inf中引用ZakoVirtualMouse的过时驱动包并pnputil /delete-driver ... /force删除(不依赖 locale 的oemN.inf命名,兼容多语言系统)。

其中745a17a0-74d3-11d0-b6fe-00a0c90f57da是 HIDClass 的标准设备类 GUID,Root\ZakoVirtualMouse是根枚举(root-enumerated)硬件 ID,表示设备由软件创建、不依赖物理总线。

5. 安装证书与驱动

certutil -addstore -f root "%CERTIFICATE%" certutil -addstore -f TrustedPublisher "%CERTIFICATE%"

先将ZakoVirtualMouse.cer同时写入受信任根与受信任发布者证书库,随后依次执行:

"%NEFCON%" --create-device-node --hardware-id Root\ZakoVirtualMouse --class-name HIDClass --class-guid 745a17a0-74d3-11d0-b6fe-00a0c90f57da "%NEFCON%" --install-driver --inf-path "%DIST_DIR%\ZakoVirtualMouse.inf"

先创建设备节点,再安装驱动。若 nefcon 安装失败(返回码非 0),自动回退到pnputil /add-driver ... /install。

6. 失败回滚与日志取证

安装失败时,脚本会:

  • 从%SystemRoot%\INF\setupapi.dev.log中提取最近 100 行与ZakoVirtualMouse、Root\ZakoVirtualMouse、ROOT\HIDCLASS相关的 SetupAPI 日志供排查;
  • 主动移除刚创建的设备节点(nefcon + pnputil 双通道),防止留下半成品设备。

最后若SERVICE_WAS_RUNNING=1且服务仍存在,则sc start SunshineService恢复服务,并以INSTALL_RESULT作为脚本退出码。

卸载脚本要点

uninstall-vmouse.bat 与安装脚本对称:

  1. 同样先停服务(sc + taskkill,并加 1 秒timeout等待句柄释放);
  2. 用 nefcon 循环删除设备节点(同样以设备计数驱动,最多 20 次迭代);
  3. nefconw.exe --uninstall-driver卸载驱动;
  4. pnputil 兜底清理“幽灵设备”(ghost device)——即 nefcon 可能遗漏的残留实例;
  5. 清理 DriverStore 中过时的ZakoVirtualMouse驱动包;
  6. 删除tools\vmouse目标目录中的驱动文件;
  7. 仅在服务原本运行且仍存在时重启服务(脚本注释说明:完整卸载流程中服务通常已被uninstall-service.bat删除,此逻辑主要为单独执行驱动重置场景保留)。

源码层面的客户端实现:驱动如何被 Sunshine 使用

驱动本身是“输入生产端”,而 Sunshine 的服务端 src/platform/windows/virtual_mouse.cpp 是“消费端”。两者的契约定义在该文件顶部的常量中(与驱动共享头vmouse_shared.h保持一致):

static constexpr uint16_t VMOUSE_VID = 0x1ACE; static constexpr uint16_t VMOUSE_PID = 0x0002; static constexpr uint8_t VMOUSE_OUTPUT_REPORT_ID = 0x02; static constexpr uint8_t VMOUSE_OUTPUT_REPORT_SIZE = 8;

HID 设备发现

客户端通过 SetupAPI 枚举全部 HID 设备接口(SetupDiGetClassDevsW+SetupDiEnumDeviceInterfaces),逐个调用HidD_GetAttributes与HidP_GetCaps校验VID=0x1ACE、PID=0x0002且 Feature Report 长度不小于 8 字节,命中后保存句柄。这也解释了冒烟测试脚本 scripts/vmouse_smoke.ps1 中匹配VID_1ACE&PID_0002的由来。

8 字节 HID 输出报告格式

鼠标数据被打包为固定 8 字节输出报告(见detail::build_output_report,virtual_mouse.cpp):

字节偏移含义
0报告 ID(0x02)
1按键状态位掩码
2–3相对 X 位移(int16 小端)
4–5相对 Y 位移(int16 小端)
6垂直滚轮(int8,正值为向上)
7水平滚轮(int8,正值为向右)

按键标志定义在 virtual_mouse.h 中:

constexpr uint8_t BTN_LEFT = 0x01; constexpr uint8_t BTN_RIGHT = 0x02; constexpr uint8_t BTN_MIDDLE = 0x04; constexpr uint8_t BTN_SIDE = 0x08; // X1 / Back constexpr uint8_t BTN_EXTRA = 0x10; // X2 / Forward

命名刻意使用BTN_*以避免与 Windows 头文件的BUTTON_LEFT/MIDDLE/RIGHT宏冲突。

累积式刷新与断线自愈

客户端采用“累积 + 2ms 定时刷新”模型:move()/button()/scroll()先把增量写入共享状态(accum_dx/accum_dy/buttonState),由高优先级刷新线程按FLUSH_INTERVAL = 2ms周期合并发送,既减少每事件一次的HidD_SetFeature开销,又逼近当前 UMDF/HID 路径的吞吐上限。

针对驱动被卸载/重装(例如pnputil /remove-device+ 重新安装)导致设备句柄失效的场景,客户端实现了两层自愈:

  • 主动重连:刷新线程每 2 秒(REOPEN_RETRY_INTERVAL)检查一次句柄,若句柄关闭则尝试重开,句柄存活则用HidD_GetPreparsedData做只读“活性探测”,探测失败即关闭陈旧句柄;
  • 发送时重连:sendReportDirect发现句柄无效时,按重试间隔尝试open()重枚举设备。

若最终仍不可用,is_available()返回 false,上层回退到SendInput路径,保证流媒体会话不中断(见 virtual_mouse.h 的 no-op fallback 说明)。

配置项与验证手段

virtual_mouse配置开关

在 src/config.cpp 中,virtual_mouse是一个布尔配置项,默认值为true(第 601 行注释:virtual mouse (use driver if available)),通过bool_f(vars, "virtual_mouse", input.virtual_mouse)解析(第 1585 行)。这意味着驱动可用时优先走硬件虚拟鼠标,驱动不可用时自动降级,与头文件声明的 fallback 策略一致。

冒烟测试

仓库提供了 scripts/vmouse_smoke.ps1 用于安装后的自动化验证,其工作流程:

  1. 定位vmouse_probe.exe(依次尝试build\tests\、build-test\tests\、out\build\tests\下的构建产物);
  2. 通过Get-PnpDevice检查Root\ZakoVirtualMouse设备状态,未安装则直接抛错终止;
  3. 运行探针程序并解析KEY=VALUE形式的输出,结合 PnP 状态(Problem=21表示“需要重启”)综合判定驱动是否就绪。

该脚本非常适合作为 CI 或人工验证步骤,在安装驱动后快速确认设备节点与输入路径均正常。

常见问题与排查路径

现象可能原因处理建议
安装报错 87(ERROR_INVALID_PARAMETER)INF 未盖章,UmdfLibraryVersion仍为$UMDFVERSION$占位符重新用 stampinf 盖章后替换 driver/ 中的 INF
安装后设备显示“需要重启”(Problem=21)驱动资源占用或签名状态待刷新使用uninstall-vmouse.bat卸载后重新安装,或重启系统
nefconw.exe未找到发布包缺少tools\nefconw.exe且无 VDD 回退路径确认工具链完整后再运行安装脚本
流媒体期间鼠标回退到软件模拟驱动句柄被外部移除(如手动卸载)客户端会在 2 秒内自动重连;确认没有运行第三方设备管理工具反复删除设备节点
卸载后残留设备节点nefcon 无法移除的“幽灵设备”卸载脚本已内置 pnputil 兜底;可手动执行pnputil /remove-device清理

小结

Zako 虚拟鼠标是 Sunshine 增强版在 Windows 平台实现“硬件级虚拟鼠标”的关键组件:driver/目录承载 WDK 构建产物(DLL/INF/CER/CAT),install-vmouse.bat与uninstall-vmouse.bat提供带脏环境检测、双工具回退、失败回滚的安装/卸载闭环,而 virtual_mouse.cpp 中 VID/PID 匹配、8 字节报告协议与断线自愈逻辑构成了驱动与主机之间的完整契约。无论你是部署者还是二次开发者,都可以依据本文从构建产物一直追踪到源码调用链,快速定位问题或扩展功能。

  • 音视频

【免费下载链接】foundation-sunshine

Sunshine fork: an enhanced sunshine, a self-hosted game streaming host for Moonlight with HDR10/HDR Vivid, virtual displays, advanced audio, optimized encoders, and a modern control panel.

项目地址:https://gitcode.com/gh_mirrors/sunshine5/foundation-sunshine
点击查看免费下载
上一篇:Ralph 多 Provider 适配器契约(ADR 0002)深度解读:三函数接口、能力声明与优雅降级规范
下一篇:claude-skills 混沌工程实战指南:基础设施故障注入的六种核心手段

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/3 13:46:06

代码改了文档没改?用 Codex 做一次全仓库文档漂移审计

代码改了文档没改?用 Codex 做一次全仓库文档漂移审计 [!NOTE] 文档漂移审计不是“让 Codex 重写所有 README”,而是以固定提交为边界,比较源码、测试、OpenAPI、示例、发布说明与现有文档,先列证据,再只更新受影响页面。 OpenAI 官方用例强调保留现有结构与术语、排除未公…

作者头像 李华
网站建设 2026/10/3 13:45:20

多项式拟合正弦曲线:机器学习入门实验,理解过拟合与正则化

简介:这份资源面向机器学习初学者与课程实验学习者,围绕多项式拟合正弦曲线这一经典课题,提供完整的Python实现与实验报告。内容涵盖最小二乘法解析解、带2范数惩罚项的正则化优化、梯度下降与共轭梯度法的手写实现,并引导读者通过…

作者头像 李华