桌面应用游戏开发【免费下载链接】HandheldCompanionA touch optimized GUI to increase your handheld gaming computer experience.项目地址https://gitcode.com/gh_mirrors/ha/HandheldCompanion点击查看免费下载本篇以 steam-hidapi.net/README.md 为主体完整保留其安装步骤与示例程序并结合仓库源码深入拆解这套 .NET HID 库如何打开 Steam Deck 手柄代号 Neptune、解析 64/65 字节输入报文、管理 Lizard 模式与触发线性震动最后给出 HandheldCompanion 主程序对该库的真实集成方式帮助读者具备绕过 Steam 输入栈、直接从 HID 层读取手柄的完整技术能力。一、库定位为 Steam Deck 手柄而生的 HID 封装README.md 将该项目定义为 Steam Deck Controller (Neptune) HID Api Library for .Net。它不是完整的输入重映射引擎而是一个薄封装层把 Windows HID 设备上的原始报文解析成结构化的按钮/摇杆状态并暴露震动、Lizard 模式、序列号等控制接口。从源码结构看整个库由三层组成hidapi.net/HidDevice.cs底层 HID 封装负责枚举、打开设备、启动读取线程、发送 feature reportsteam-hidapi.net/SteamController.cs手柄基类封装打开/关闭、读取序列号、写寄存器、Lizard 模式与震动等公共逻辑steam-hidapi.net/NeptuneController.cs 与 steam-hidapi.net/GordonController.cs分别对应 Steam Deck 手柄Neptune与 Steam 无线手柄Gordon两个具体型号。项目文件 steam-hidapi.net/steam-hidapi.net.csproj 显示该库目标框架为 .NET Framework 4.8Debug 与 Release 两个配置均编译为x64平台这与 README 中必须 x64的要求一致。二、安装与配置步骤完整继承 READMEREADME How to use 章节给出的接入步骤如下本文逐条保留并补充说明从 releases 部分下载 .dll 文件拷贝到项目根目录在解决方案资源管理器中右键该文件选择属性将复制到输出目录Copy to Output Directory设置为始终复制Always copy to output dir确认项目构建目标是 x64 平台。如果不是进入 Build Configuration Manager新增一个 x64 配置参考下方示例程序获取手柄输入并输出到控制台。补充两点实操细节仓库内该库本身是源码工程steam-hidapi.net目录因此也可以跳过下载 dll直接以项目引用方式接入。主程序 HandheldCompanion/HandheldCompanion.csproj 就是这样做的ProjectReference Include..\steam-hidapi.net\steam-hidapi.net.csproj /x64 要求来自 hidapi 原生层底层通过 P/Invoke 调用hidapi.dll见 hidapi.net/Native/HidApiNative.csx86 进程无法稳定加载 64 位原生依赖。三、README 示例程序读取手柄输入并输出到控制台README 给出的完整示例如下原文照录用于说明事件订阅、Lizard 模式、设备打开与序列号读取的基本用法using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace neptune_hidapi.net { internal class Program { static void Main(string[] args) { NeptuneController controller new NeptuneController(); controller.OnControllerInputReceived Controller_OnControllerInputReceived; controller.LizardModeEnabled true; //Mouse and Keyboard emulation enabled. controller.Open(); //Open the controller device. Console.WriteLine($Controller Serial: {controller.SerialNumber}); // Output the serial number. Only available once device has been opened. Console.ReadLine(); } static DateTime lastUpdate DateTime.Now; private static void Controller_OnControllerInputReceived(object sender, NeptuneControllerInputEventArgs e) { if ((DateTime.Now - lastUpdate).TotalMilliseconds 100) { Console.CursorTop 0; Console.CursorLeft 0; foreach (var btn in e.State.ButtonState.Buttons) { Console.WriteLine(${btn}: {e.State.ButtonState[btn]} ); } foreach (var axis in e.State.AxesState.Axes) { Console.WriteLine(${axis}: {e.State.AxesState[axis]} ); } lastUpdate DateTime.Now; } } } }示例的关键点OnControllerInputReceived委托绑定后手柄每产生一帧输入报文就会触发一次回调回调内用 100ms 节流 光标回位的方式把 28 个按钮Buttons与 22 个轴向Axes的当前值打印成仪表盘SerialNumber只有在Open()成功之后才有值——源码中Open()会先调ReadSerialNumber()再启动读取见 SteamController.cs。示例与当前源码的签名差异重要README 示例写作早于代码演进与当前仓库源码相比有两处出入按当前源码实际编写时应以 NeptuneController.cs 为准构造函数不再无参而是NeptuneController(ushort vid, ushort pid, ushort inputBufferLen, short index)另有携带devicePath的重载。Steam Deck 手柄的 VID/PID 为0x28DE / 0x1205在 Hid/HidEnums.cs 的SCPid.STEAMDECK中定义inputBufferLen需匹配固件的输入报文长度主程序实践中会依次尝试 65 与 64详见第六节事件委托是ActionNeptuneControllerInputEventArgs没有sender参数Lizard 模式的运行期切换接口是RequestLizardMode(bool)而LizardModeEnabled只是只读属性。按当前源码改写后的最小可用版本大致是// Steam Deck: VID 0x28DE, PID 0x1205, 输入报文长度 65新固件或 64旧固件 var controller new NeptuneController(0x28DE, 0x1205, 65, -1); controller.OnControllerInputReceived e { bool A e.State.ButtonState[NeptuneControllerButton.BtnA]; short lx e.State.AxesState[NeptuneControllerAxis.LeftStickX]; }; controller.Open(); Console.WriteLine($Serial: {controller.SerialNumber}); controller.RequestLizardMode(true); // 重新启用系统级鼠标/键盘仿真四、输入事件链从 HID 报文到结构化状态4.1 读取线程与事件分发Open()的调用链是SteamController.Open()→HidDevice.OpenDevice()打开设备 →ReadSerialNumber()→_hidDevice.BeginRead()。BeginRead()在 hidapi.net/HidDevice.cs 中启动一个AboveNormal优先级的后台读取线程ReadLoop循环调用hid_read_timeout每收到一帧报文就回调OnInputReceived。Neptune 子类在 NeptuneController.cs 中做报文过滤与分发校验报文头Buffer[0] 1 Buffer[1] 0否则丢弃按Buffer[2]的事件类型SCEventType分支INPUT_DATA (0x01)Gordon 手柄的普通输入Deck 上忽略DECK_INPUT_DATA (0x09)Steam Deck 专属输入帧——把报文通过ToStructureNCInput()转成结构体构造NeptuneControllerInputState后触发OnControllerInputReceivedCONNECT (0x03)/BATTERY (0x04)预留分支当前未使用。事件类型定义见 HidEnums.cs。4.2 NCInput 报文结构NCInput是一个Pack 1的 64 字节顺序布局结构体HidEnums.cs字节偏移注释清晰偏移字段含义0x00–0x03ptype/_a1/_a2/_a3报文类型与事件头0x04seq序列号UInt320x08–0x0A, 0x0B, 0x0D, 0x0Ebuttons0/1/2/3/5/66 个按钮位域字节0x10–0x16lpad_x/y,rpad_x/y左右触控板坐标Int160x18–0x1Caccel_x/y/z加速度计0x1E–0x22gpitch/gyaw/groll陀螺仪0x24–0x2Aq1–q4姿态四元数0x2C–0x2Eltrig/rtrigL2/R2 扳机Int160x30–0x36lthumb_x/y,rthumb_x/y左右摇杆Int160x38–0x3Alpad_pressure/rpad_pressure触控板按压压力ToStructureT()是 Util/StructUtils.cs 中的扩展方法内部用GCHandle.PinnedMarshal.PtrToStructure零拷贝完成字节数组到结构体的转换性能开销很低。4.3 按钮位域解码按钮不是枚举值而是分布在 6 个字节里的独立 bit。以NCButton0为例HidEnums.csBTN_A 0b10000000、BTN_X 0b01000000、BTN_B 0b00100000、BTN_Y 0b00010000、BTN_L1/L2/R1/R2占据低 4 位。解码逻辑在 NeptuneControllerInputState.cs逐位与掩码比较后写入DictionaryNeptuneControllerButton, bool。完整按钮集合28 个见 NeptuneControllerEnums.cs枚举值枚举值BtnX/BtnY/BtnA/BtnB1–4BtnL1/BtnR1/BtnL2/BtnR213–16BtnMenu/BtnOptions/BtnSteam/BtnQuickAccess5–8BtnL4/BtnR4/BtnL5/BtnR517–20BtnDpadUp/Left/Right/Down9–12触控板按压/触摸R/L Pad Press/Touch21–24摇杆按压/触摸R/L Stick Press/Touch25–28轴向集合22 个左右摇杆 X/Y、左右触控板 X/Y、触控板压力、L2/R2、加速度 XYZ、陀螺仪 Roll/Pitch/Yaw 及四元数 Q1–Q4。状态对象暴露两种访问方式正好对应 README 示例的两种遍历ButtonState[NeptuneControllerButton.BtnA]/AxesState[NeptuneControllerAxis.LeftStickX]索引器按钮不存在时返回false轴向不存在时返回0取值安全ButtonState.Buttons/AxesState.Axes枚举全部键适合全量打印这类调试场景。五、Lizard 模式系统级键鼠仿真开关Lizard 模式是 Valve 为手柄提供的默认鼠标/键盘仿真开启后 Windows 会把手柄当作带触控板的设备触控板可直接移动系统光标关闭后手柄从系统输入链中退隐只通过 feature report 与你的程序通信。5.1 实现原理SetLizardMode位于 SteamController.cs两条路径都通过WriteSingleCmd发送 feature report 命令开启发送DEFAULT_MAPPINGS (0x85)DEFAULT_MOUSE (0x8e)恢复默认按键映射与鼠标行为关闭发送CLEAR_MAPPINGS (0x81)再把LPAD_MODE (0x07)与RPAD_MODE (0x08)两个寄存器写为SCLizardMode.OFF (0x07)。寄存器与命令码的完整定义在 HidEnums.csSCPacketType、SCRegister注释还标明了各寄存器的用途如RPAD_MARGIN (0x18)用于消除触控板小幅抖动噪声、LED_INTENSITY (0x2d)取值 0–100。5.2 心跳循环为什么存在Neptune 与 Gordon 的一个重要差别Neptune 在Open()后必须维持一个心跳任务NeptuneController.cs。ConfigureLoop每 1000ms 醒一次若_lizardDirty或当前处于关闭态就重新下发SetLizardMode(_lizard)。从源码结构看这是为了应对 Deck 手柄固件可能随时把映射抢回默认态的行为——通过周期性重放配置保证 Lizard 状态与请求值一致。对外切换入口是RequestLizardMode(bool)它只置脏标记并Set()一个AutoResetEvent让心跳线程在下一拍执行实际写操作而不是在调用线程里直接写设备避免与读取线程抢 HID 句柄。Close()时会等待心跳任务退出并补发最后一次SetLizardMode(_lizard)NeptuneController.cs注释明确说明防止循环未来得及执行最后一次请求。六、Feature Report序列号、寄存器与震动6.1 发送/接收机制所有控制面通信都走 feature report。hidapi.net/HidDevice.cs 的RequestFeatureReport先在请求数据前补 1 字节 report ID调用hid_send_feature_report发送再用hid_get_feature_report取回响应全程持有锁失败设备无效、报文超长、原生调用返回负值统一返回null调用方据此判空。序列号读取SteamController.cs发送{GET_SERIAL (0xae), 0x15, 0x01}从响应偏移 4 处截取 ASCII 字符串任何异常都静默降级为XXXXX保证Open()流程不会因读序列号失败而中断。6.2 震动SetHaptic 与 SetHaptic2库提供两代震动接口SetHaptic(byte position, ushort amplitude, ushort period, ushort count, sbyte gain)SteamController.cs构造SCHapticPacket0x8f包参数为幅度、周期、重复次数gain单位是分贝源码注释 -24..6SetHaptic2(SCHapticMotor position, NCHapticStyle style, sbyte intensity)NeptuneController.csNeptune 专用0xea包len 0xdNCHapticStyle取Disabled/Weak/Strongintensity注释为 -7..5 对应约 -2dB..10dB并附带两个时间戳字段。源码中也坦诚标注该包结构需要 USBpcap 抓包验证。七、GordonSteam 无线手柄的扩展能力同一套SteamController基类还支撑 Gordon 手柄有线 PID0x1102、无线0x1142见SCPid。GordonController.cs 在 Neptune 之外多出的能力包括SetGyroscope(bool)通过GYRO_MODE (0x30)寄存器位域GCGyroModeTILT_X/TILT_Y/Q/ACCEL/GYRO组合开启加速度计 陀螺仪SetIdleTimeout(ushort)写IDLE_TIMEOUT (0x32)寄存器秒SetLedIntensity(byte)写LED_INTENSITY超过 100 自动钳位TurnOff()发送{OFF (0x9f), 0x04, 0x6f, 0x66, 0x66, 0x21}直接给手柄下电电池事件SCEventType.BATTERY帧中偏移 12 处是电压UInt16、偏移 14 处是百分比GordonController.cs断连恢复CONNECT事件状态字节0x02表示重新连上此时自动重放此前的 Lizard/陀螺仪/空闲超时配置GordonController.cs。Gordon 的输入报文结构GCInputHidEnums.cs与 Neptune 相近但按钮只有 3 个字节且没有触控板压力字段其左键鼠/摇杆复用逻辑LPAD_AND_JOY位在 GordonControllerInputState.cs 中有专门的合并处理。八、HandheldCompanion 的真实集成方式主程序把该库当作输入源适配层使用集成点可以对照阅读8.1 设备识别ControllerManager.cs 按 VID/PID 分发0x28DE/0x1102MI2 时与0x1142创建GordonController0x1205Steam Deck与0x12f0SteamOS 掌机手柄创建NeptuneController。注释还说明 Steam Controller 是三接口复合 HID 设备Valve 的 feature-report 通道位于接口 2。8.2 固件兼容与防错开虚拟设备封装类 HandheldCompanion/Controllers/Steam/NeptuneController.cs 展示了两处关键工程细节缓冲区长度回退依次用 65、64 两种inputBufferLen尝试构造并Open()成功后才继续——注释写明支持不同固件版本65 当前64 旧版。而HidDevice.OpenDevice()正是靠比较HidP_GetCaps得到的InputReportByteLength与期望长度来决定是否选中某设备hidapi.net/HidDevice.csdevicePath 直开构造时传入details.devicePathOpenDevice()会优先hid_open_path确保打开的是真实物理设备而不是同 VID/PID 的虚拟设备HidDevice.cs。8.3 Lizard 模式的取舍主程序的策略与纯监控场景不同Open()时RequestLizardMode(false)关闭系统仿真避免 Deck 触控板同时驱动系统光标Close()前RequestLizardMode(true)还原让手柄离开程序管理后恢复默认行为NeptuneController.cs。8.4 输入帧的消费Tick()每帧把库解析出的状态映射进主程序统一输入模型NeptuneController.cs扳机值L2/R2Int16归一化到 0–255并按Gamepad.TriggerThreshold派生 Soft/Full 两个虚拟按钮触控板未触摸时坐标强制清零点击方向用内部阈值TrackPadInner 21844判定上/下/左/右四个合成键陀螺仪 16 位量程按 ±2000 dps、加速度计按 ±2g 换算成物理量后送入GyroState再交给GamepadMotion处理震动走独立后台线程RumbleThreadLoop以SteamControllerRumbleInterval默认 10ms为周期把 XInput 风格的大/小马达字节经GetHapticIntensity线性映射到-2..10的NCHapticStyle.Weak强度调SetHaptic2下发NeptuneController.cs。SetHaptic中还按HapticStrength分档映射强度 -7/-1/5松开时再衰减 3dB。九、使用限制与注意事项结合源码与项目配置使用该库需要满足以下前提平台仅 Windows依赖 hidapi 原生库与HidD_GetPreparsedData等 Win32 API且宿主进程必须是 x64框架.NET Framework 4.8C# 7.3 语言版本生命周期Open()可能抛异常Could not open device!调用方应捕获Close()会停止读取线程并等待心跳任务退出务必成对调用线程模型输入回调发生在 HID 读取线程内事件处理器中不应做 UI 更新或阻塞操作Lizard 状态要自觉还原关闭 Lizard 后手柄不再向系统报告键鼠程序崩溃时用户手柄失灵参考主程序的做法是在退出路径恢复RequestLizardMode(true)报文版本差异Steam Deck 固件升级可能改变输入报文长度65/64跨固件场景应像主程序一样做多长度回退README 示例时效如第三节所述README 示例的无参构造与带sender的事件签名属于早期版本写法集成时以仓库当前源码签名为准。十、小结steam-hidapi.net用不到千行代码搭起了一条Windows HID → Steam 系手柄原始报文 → 结构化输入状态的完整通道HidDevice负责设备与读取线程SteamController基类负责 feature report 控制面Lizard 模式、序列号、寄存器、震动NeptuneController/GordonController分别实现两套输入报文的解码与型号专属能力。HandheldCompanion 在其上完成了固件兼容回退、Lizard 模式管理、扳机/触控板/体感换算与线性震动循环等生产级处理这套库 封装的组合模式对任何需要绕过游戏层、直接读取 Steam Deck 或 Steam 手柄 HID 报文的 .NET 项目都有直接的参考价值。赞分享桌面应用游戏开发【免费下载链接】HandheldCompanionA touch optimized GUI to increase your handheld gaming computer experience.项目地址https://gitcode.com/gh_mirrors/ha/HandheldCompanion点击查看免费下载相关推荐HandheldCompanion hidapi.net.NET HID 设备封装库的使用与底层实现解析HandheldCompanion hidapi.net.NET HID 设备封装库的使用与底层实现解析 hidapi.net 是 HandheldCompa桌面应用游戏开发Steam Deck Tools在 Windows 上驾驭 Steam Deck 的风扇控制、性能悬浮窗、功耗控制与手柄驱动Steam Deck Tools在 Windows 上驾驭 Steam Deck 的风扇控制、性能悬浮窗、功耗控制与手柄驱动 Steam Deck Tools桌面应用系统底层Steam Deck Tools解锁Windows在Steam Deck上的无限可能Steam Deck Tools解锁Windows在Steam Deck上的无限可能 项目介绍 Steam Deck Tools 是一个专为Steam Dec桌面应用系统底层上一篇终极指南使用WeChatMsg实现微信聊天记录永久保存与智能分析下一篇OBS Studio启动失败终极指南从崩溃闪退到流畅直播的完整修复方案创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考