- 音视频
【免费下载链接】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.
导读
本文以 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.dll | UMDF 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 构建驱动的标准流程
原文档给出的构建步骤非常简洁,展开后是完整的四步:
- 在 Visual Studio 中打开驱动源码工程
drivers/virtual_mouse/ZakoVirtualMouse.sln; - 将解决方案配置切换为Release,平台选择x64(UMDF 驱动必须面向 64 位系统);
- 构建(Build)整个解决方案;
- 将构建输出目录
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 与安装脚本对称:
- 同样先停服务(sc + taskkill,并加 1 秒
timeout等待句柄释放); - 用 nefcon 循环删除设备节点(同样以设备计数驱动,最多 20 次迭代);
nefconw.exe --uninstall-driver卸载驱动;- pnputil 兜底清理“幽灵设备”(ghost device)——即 nefcon 可能遗漏的残留实例;
- 清理 DriverStore 中过时的
ZakoVirtualMouse驱动包; - 删除
tools\vmouse目标目录中的驱动文件; - 仅在服务原本运行且仍存在时重启服务(脚本注释说明:完整卸载流程中服务通常已被
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 用于安装后的自动化验证,其工作流程:
- 定位
vmouse_probe.exe(依次尝试build\tests\、build-test\tests\、out\build\tests\下的构建产物); - 通过
Get-PnpDevice检查Root\ZakoVirtualMouse设备状态,未安装则直接抛错终止; - 运行探针程序并解析
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.
相关推荐
Windows 驱动示例解析:基于 UMDF 2 的 HID Minidriver 虚拟设备与自定义 Feature Report 通信
Windows 驱动示例解析:基于 UMDF 2 的 HID Minidriver 虚拟设备与自定义 Feature Report 通信 导读 本文以 Wind
示例工程Windows 10虚拟鼠标键盘驱动终极安装指南
Windows 10虚拟鼠标键盘驱动终极安装指南 虚拟鼠标键盘驱动是一款功能强大的驱动程序,能够在Windows 10系统下通过内核模式驱动执行精确的鼠标和键盘
驱动开发【亲测免费】 虚拟多路HID驱动(Virtual Multiple HID Driver)安装教程
虚拟多路HID驱动 Virtual Multiple HID Driver 安装教程 1. 项目介绍 虚拟多路HID驱动( vmulti https://git
驱动开发
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考