Xbox One 控制器转换为 macOS HID 设备的工作原理分析

源代码在 https://github.com/guilhermearaujo/xboxonecontrollerenabler.git

这个工程的核心功能是将 Xbox One 控制器(macOS 原生不支持的设备)转换为 macOS 可识别的 HID 设备。这里通过分析代码,详细解释其工作原理、设备描述和报告描述符的实现。

整体架构

该项目由三个主要部分组成:

  1. Xbox 控制器通信层:通过 IOKit 框架与 Xbox One 控制器进行 USB 通信
  2. 虚拟 HID 设备层:使用 VHID 框架创建虚拟 HID 设备
  3. 系统集成层:使用 WirtualJoy 框架将虚拟设备注册到 macOS 系统

Xbox One 控制器设备描述

Xbox One 控制器的设备描述在代码中通过 XboxOneButtonMap 结构体定义:

typedef struct {bool sync;bool dummy; // Always 0.bool menu;  // Not entirely sure what these arebool view;  // called on the new controllerbool a;bool b;bool x;bool y;bool dpad_up;bool dpad_down;bool dpad_left;bool dpad_right;bool bumper_left;bool bumper_right;bool stick_left_click;bool stick_right_click;unsigned short trigger_left;unsigned short trigger_right;short stick_left_x;short stick_left_y;short stick_right_x;short stick_right_y;bool home;
} XboxOneButtonMap;

这个结构体映射了 Xbox One 控制器的所有输入元素,包括:

  • 按钮(A、B、X、Y、方向键、肩键等)
  • 摇杆(左右摇杆的 X/Y 坐标)
  • 扳机键(左右扳机的模拟值)

控制器通信实现

GAXboxControllerCommunication 类负责与 Xbox One 控制器通信:

  1. 通过 USB 供应商 ID (0x045e) 和产品 ID (0x02d1) 识别 Xbox One 控制器
  2. 使用 IOKit 框架打开设备并配置接口
  3. 初始化控制器并开始轮询数据
  4. poll 方法中读取原始数据并解析为 XboxOneButtonMap 结构

关键代码片段:


- (void)poll {while (shouldPoll) {UInt32 numBytes = 20;char dataBuffer[32];returnCode = (*usbInterface)->ReadPipe(usbInterface, 2, dataBuffer, &numBytes);if (numBytes == 18) {Byte b = dataBuffer[4];buttonMap.sync  = (b & (1 << 0)) != 0;buttonMap.dummy = (b & (1 << 1)) != 0;buttonMap.menu  = (b & (1 << 2)) != 0;buttonMap.view  = (b & (1 << 3)) != 0;buttonMap.a = (b & (1 << 4)) != 0;buttonMap.b = (b & (1 << 5)) != 0;buttonMap.x = (b & (1 << 6)) != 0;buttonMap.y = (b & (1 << 7)) != 0;b = dataBuffer[5];buttonMap.dpad_up    = (b & (1 << 0)) != 0;buttonMap.dpad_down  = (b & (1 << 1)) != 0;buttonMap.dpad_left  = (b & (1 << 2)) != 0;buttonMap.dpad_right = (b & (1 << 3)) != 0;buttonMap.bumper_left       = (b & (1 << 4)) != 0;buttonMap.bumper_right      = (b & (1 << 5)) != 0;buttonMap.stick_left_click  = (b & (1 << 6)) != 0;buttonMap.stick_right_click = (b & (1 << 7)) != 0;buttonMap.trigger_left  = (dataBuffer[7] << 8) + (dataBuffer[6] & 0xff);buttonMap.trigger_right = (dataBuffer[9] << 8) + (dataBuffer[8] & 0xff);buttonMap.stick_left_x  = (dataBuffer[11] << 8) + dataBuffer[10];buttonMap.stick_left_y  = (dataBuffer[13] << 8) + dataBuffer[12];buttonMap.stick_right_x = (dataBuffer[15] << 8) + dataBuffer[14];buttonMap.stick_right_y = (dataBuffer[17] << 8) + dataBuffer[16];[delegate controllerDidUpdateData:buttonMap];}else if (numBytes == 6) {buttonMap.home = dataBuffer[4] & 1;[delegate controllerDidUpdateData:buttonMap];}[NSThread sleepForTimeInterval:0.005f];}
}

虚拟 HID 设备实现

VHIDDevice 类负责创建虚拟 HID 设备,它通过组合 VHIDButtonCollectionVHIDPointerCollection 来管理按钮和指针(摇杆)状态:

- (id)initWithType:(VHIDDeviceType)typepointerCount:(NSUInteger)pointerCountbuttonCount:(NSUInteger)buttonCountisRelative:(BOOL)isRelative
{self = [super init];m_Type      = type;m_Buttons   = [[VHIDButtonCollection alloc] initWithButtonCount:buttonCount];m_Pointers  = [[VHIDPointerCollection alloc] initWithPointerCount:pointerCountisRelative:isRelative];// ... 初始化状态数据m_Descriptor = [[self createDescriptor] retain];return self;
}

HID 报告描述符生成

VHIDDevice 类的 createDescriptor 方法负责生成 HID 报告描述符,这是关键部分:

- (NSData*)createDescriptor
{BOOL             isMouse        = (m_Type == VHIDDeviceTypeMouse);NSData          *buttonsHID     = [m_Buttons descriptor];NSData          *pointersHID    = [m_Pointers descriptor];NSMutableData   *result         = [NSMutableData dataWithLength:[buttonsHID length] +[pointersHID length] +((isMouse)?(HIDDescriptorMouseAdditionalBytes):(HIDDescriptorJoystickAdditionalBytes))];unsigned char   *data           = [result mutableBytes];unsigned char    usage          = ((isMouse)?(0x02):(0x05));*data = 0x05; data++; *data = 0x01; data++;      // USAGE_PAGE (Generic Desktop)*data = 0x09; data++; *data = usage; data++;     // USAGE (Mouse/Game Pad)*data = 0xA1; data++; *data = 0x01; data++;      // COLLECTION (Application)// ... 添加按钮和指针描述符*data = 0xC0; data++; // END_COLLECTION*data = 0xC0; data++; // END_COLLECTIONreturn result;
}

这个方法创建了一个标准的 HID 报告描述符,定义了设备类型(游戏手柄)、按钮和指针(摇杆)的布局。

系统集成

WJoyDeviceWJoyDeviceImpl 类负责将虚拟 HID 设备注册到 macOS 系统:

  1. 加载内核驱动程序
  2. 创建与驱动程序的连接
  3. 设置设备属性(产品名称、供应商 ID、产品 ID 等)
  4. 启用设备并更新 HID 状态

关键代码:

- (id)initWithHIDDescriptor:(NSData*)HIDDescriptor properties:(NSDictionary*)properties
{// ... 初始化代码m_Impl = [[WJoyDeviceImpl alloc] init];// 设置设备属性if(productString != nil)[m_Impl setDeviceProductString:productString];// ... 设置其他属性// 启用设备if(![m_Impl enable:HIDDescriptor]){[self release];return nil;}return self;
}

数据流转换过程

整个数据流转换过程如下:

  1. GAXboxControllerCommunication 从 Xbox One 控制器读取原始 USB 数据
  2. 数据被解析为 XboxOneButtonMap 结构体
  3. GAXboxController 处理这些数据并提供高级访问方法
  4. GAMainViewController 将控制器数据映射到虚拟 HID 设备:
- (void)updateVHID:(GAXboxController *)controller {[_VHID setButton:0 pressed:[controller A]];[_VHID setButton:1 pressed:[controller B]];// ... 设置其他按钮NSPoint point = NSZeroPoint;point.x = [controller leftAnalogX];point.y = [controller leftAnalogY];[_VHID setPointer:0 position:point];// ... 设置其他指针
}
  1. VHIDDevice 更新其内部状态并通知代理
  2. WJoyDevice 将更新后的 HID 状态发送到系统

报告提交流程

  1. VHIDDevice.m 中的状态更新和报告生成
    当按钮或指针状态发生变化时,VHIDDevice会生成新的状态报告:

    - (void)setButton:(NSUInteger)buttonIndex pressed:(BOOL)pressed {// ... 检查按钮状态是否变化[m_Buttons setButton:buttonIndex pressed:pressed];if(m_Delegate != nil)[m_Delegate VHIDDevice:self stateChanged:[self state]];
    }
    
    - (NSData*)state {unsigned char *data = [m_State mutableBytes];NSData *buttonState = [m_Buttons state];NSData *pointerState = [m_Pointers state];// 合并按钮和指针状态到一个完整的HID报告if(buttonState != nil) {memcpy(data, [buttonState bytes], [buttonState length]);}if(pointerState != nil) {memcpy(data + [buttonState length], [pointerState bytes], [pointerState length]);}return [[m_State retain] autorelease];
    }
    
  2. VHIDButtonCollection.m 和 VHIDPointerCollection.m
    这两个类负责维护按钮和指针的状态,并生成对应的HID报告部分:

    在VHIDButtonCollection中:

    - (void)setButton:(NSUInteger)buttonIndex pressed:(BOOL)pressed {// ... 检查按钮索引NSUInteger buttonByte = buttonIndex / 8;NSUInteger buttonBit = buttonIndex % 8;unsigned char *data = (unsigned char*)[m_State mutableBytes];// 设置对应位的按钮状态if(pressed)data[buttonByte] |= buttonMasks[buttonBit];elsedata[buttonByte] &= ~(buttonMasks[buttonBit]);
    }
    

    在VHIDPointerCollection中:

    - (void)setPointer:(NSUInteger)pointerIndex position:(NSPoint)position {// ... 检查指针索引char *data = (char*)[m_State mutableBytes] + pointerIndex * HIDStatePointerSize;// 设置X和Y坐标值*data = [VHIDPointerCollection clipCoordinateTo:position.x];*(data + 1) = -[VHIDPointerCollection clipCoordinateTo:position.y];
    }
    
  3. GAMainViewController.m 中的代理方法
    当VHIDDevice状态变化时,通过代理方法将状态传递给WJoyDevice:

    - (void)VHIDDevice:(VHIDDevice *)device stateChanged:(NSData *)state {[_virtualDevice updateHIDState:state];
    }
    
  4. WJoyDevice.m 中的更新方法
    最后,WJoyDevice将HID状态报告提交给系统:

    - (BOOL)updateHIDState:(NSData*)HIDState {return [m_Impl updateState:HIDState];
    }
    

总结

这个工程通过以下步骤将 Xbox One 控制器转换为 macOS 可识别的 HID 设备:

  1. 使用 IOKit 框架与 Xbox One 控制器通信,读取原始输入数据
  2. 将这些数据解析为结构化的按钮和摇杆状态
  3. 创建一个虚拟 HID 设备,生成标准的 HID 报告描述符
  4. 将控制器状态映射到虚拟 HID 设备状态
  5. 通过内核驱动程序将虚拟设备注册到系统

这种方法允许 macOS 将 Xbox One 控制器识别为标准游戏手柄,从而在不需要官方驱动的情况下实现兼容性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/87391.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/87391.shtml
英文地址,请注明出处:http://en.pswp.cn/web/87391.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Notepad++ 复制宏、编辑宏的方法

Notepad具有宏的功能&#xff0c;能够记录当下所有操作&#xff0c;后续只需要一键就可以重复执行&#xff0c;大大减少工作量。 比如我需要把很多文件里面的字符完成替换&#xff0c;那我只需要把替换的过程录制成宏&#xff0c;后续打开文件就可以一键替换了。 但是Notepad的…

Oracle:报错jdbc:oracle:thin:@IP地址:端口:实例名, errorCode 28001, state 99999

报错原因是oracle密码过期&#xff0c;根本解决办法是让密码不再过期&#xff0c;永久有效。具体操作记录一下。 cmd命令行输入&#xff1a; sqlplus / as sysdba修改Oracle密码期限为无限&#xff1a; SQL> ALTER PROFILE DEFAULT LIMIT PASSWORD_LIFE_TIME UNLIMITED;SQL&…

Apipost 签约中原消费金融:共建企业级 API 全链路协作平台,推动接口管理与测试智能化升级

随着企业数字化转型的不断深化&#xff0c;API 正在从技术细节演变为业务协作的核心枢纽。特别是在金融行业&#xff0c;微服务架构、系统联动、合规要求等多重因素交织下&#xff0c;接口数量激增、管理复杂度提升、质量保障难度加大。近日&#xff0c;Apipost 与中原消费金融…

AntV L7 之LarkMap 地图

一、安装$ npm install -S antv/l7 antv/larkmap # or $ yarn add antv/l7 antv/larkmap二、引入包import type { LarkMapProps, LineLayerProps } from antv/larkmap; import { LarkMap, LineLayer, Marker } from antv/larkmap;三、config配置const layerOptions:Omit<Lin…

客户案例 | 某新能源车企依托Atlassian工具链+龙智定制开发服务,打造符合ASPICE标准的研发管理体系

客户案例 ASPICE标准已成为衡量整车厂及供应商研发能力的重要标尺。某知名车企在其重点项目研发过程中&#xff0c;面临着ASPICE 4.0评估认证的挑战——项目团队缺乏体系经验、流程规范和数字化支撑工具。 为帮助该客户团队顺利通过ASPICE认证并提升研发合规性&#xff0c;At…

stm32的USART使用DMA配置成循环模式时发送和接收有着本质区别

stm32的USART使用DMA配置成循环模式时发送和接收有着本质区别&#xff0c;不要被网上误导了。发送数据时会不停的发送数据&#xff0c;而接收只有有数据时才会接收&#xff0c;没有数据时就会挂起等待。 一、触发机制的差异‌ ‌发送方向&#xff08;TX&#xff09;——状态驱…

银河麒麟系统上利用WPS的SDK进行WORD的二次开发

目录 1.下载安装包 2.安装WPS 3.获取示例代码 4.编译示例代码 5.完整示例代码 相关链接 1.下载安装包 去wps的官网 https://www.wps.cn/ 下载linux版本。 下载的安装包名称为&#xff1a;wps-office_12.8.2.21176.AK.preload.sw_amd64.deb, 官网有介绍适用于Ubuntu、麒麟…

人工智能之数学基础:如何判断正定矩阵和负定矩阵?

本文重点 正定矩阵和负定矩阵是线性代数中的重要概念,在优化理论、数值分析、统计学等领域有广泛应用。 正定矩阵(负定矩阵) 如上所示,我们可以看到满足上面的性质的时候,我们可以认为矩阵A称为正定矩阵(负定矩阵) 举例: 半正定(半负定) 如果≥或者≤的时候,我们认为矩…

汇编基础介绍——ARMv8指令集(四)

一、CMP 指令 CMP 指令用来比较两个数的大小。在 A64 指令集的实现中&#xff0c;CMP 指令内部调用 SUBS 指令来实现。 1.1、使用立即数的 CMP 指令 使用立即数的 CMP 指令的格式如下。 CMP <Xn|SP>, #<imm>{, <shift>} 上述指令等同于如下指令。 SUBS …

深入剖析 Electron 性能瓶颈及优化策略

Electron 是一个流行的跨平台桌面应用开发框架&#xff0c;基于 Chromium 和 Node.js&#xff0c;使得开发者可以使用 Web 技术&#xff08;HTML、CSS、JavaScript&#xff09;构建跨平台的桌面应用。许多知名应用如 VS Code、Slack、Discord 和 Figma 都采用了 Electron。然而…

Qt的前端和后端过于耦合(0/7)

最近在写一个软件&#xff0c;这个软件稍微复杂一些&#xff0c;界面大概需要十几个&#xff0c;后端也是要开多线程读各种传感器数据。然后鼠鼠我呀就发现一个致命的问题&#xff0c;那就是前端要求的控件太多了&#xff0c;点一下就需要通知后端&#xff0c;即调用后端的函数…

碰一碰发视频源码搭建定制化开发:支持OEM

在移动互联网与物联网深度融合的当下&#xff0c;“碰一碰发视频” 作为一种创新的信息交互方式&#xff0c;正逐渐应用于营销推广、产品展示、社交互动等多个领域。其核心在于通过近场通信技术&#xff08;如 NFC、蓝牙&#xff09;实现设备间的快速连接&#xff0c;无需复杂操…

机器学习文本特征提取:CountVectorizer与TfidfVectorizer详解

一、文本特征提取概述 在自然语言处理&#xff08;NLP&#xff09;和文本挖掘任务中&#xff0c;文本特征提取是将原始文本数据转换为机器学习模型可以理解的数值特征的关键步骤。scikit-learn提供了两种常用的文本特征提取方法&#xff1a;CountVectorizer&#xff08;词频统…

【PHP】.Hyperf 框架-collection 集合数据(内置函数归纳-实用版)

&#x1f4cc; Article::query()->where(article_id, 6)->select()->first()✍️ 进行数据结果的循环&#xff0c;遍历 1.each() 方法遍历集合中的项目并将每个项目传递给闭包&#xff0c;进行处理数据 Article::query()->get()->each(function ($item) {// 可…

巨兽的阴影:大型语言模型的挑战与伦理深渊

当GPT-4这样的庞然大物能够流畅对话、撰写诗歌、编写代码、解析图像&#xff0c;甚至在某些测试中媲美人类专家时&#xff0c;大型语言模型&#xff08;LLM&#xff09;仿佛成为了无所不能的“智能神谕”。然而&#xff0c;在这令人目眩的成就之下&#xff0c;潜藏着复杂而严峻…

vue根据链接生成二维码 qrcode

vue根据链接生成二维码 qrcode js 需求&#xff1a;后端返回一个完整链接&#xff0c;前端根据链接生成一个二维码 1、安装qrcode插件 npm install qrcode2、引入qrcode,并且使用完整代码 <template> <div><img :src"qrcodeData" class"qrcode…

C# 事件(源代码组件概览)

源代码组件概览 需要在事件中使用的代码有5部分&#xff0c;如图15-4所示&#xff0c;后文会依次进行介绍。这些组件如下 所示 委托类型声明事件和事件处理程序必须有共同的签名和返回类型&#xff0c;它们通过委托类型 进行描述。事件处理程序声明订阅者类中会在事件触发时执…

音视频会议服务搭建(设计方案-数据库sql)-02

前言 衔接上篇文章&#xff0c;这篇是相关的表结构sql语句记录 EchoMeet 会议系统数据库表结构设计 &#x1f4cb; 设计概述 本文档定义了EchoMeet音视频会议系统的完整数据库表结构&#xff0c;采用微服务架构设计&#xff0c;支持高并发、可扩展的会议场景。 &#x1f3af…

MCPA2APPT 智能化演示文稿系统:A2A、MCP、ADK 三大架构全流程自动化

&#x1f680; 项目名称 MCPA2APPT / MultiAgentPPT —— 一站式 A2A MCP ADK 多智能体并发 PPT 生成解决方案 MCPA2APPT 是一款开源 AI PPT 创作神器&#xff0c;基于 A2A&#xff08;Ask-to-Answer&#xff09;、MCP&#xff08;Multi-agent Control Protocol&#xff09;和…

pyinstall打包mysql-connector-python后运行报错的问题!

简单的测试代码 # main.py import mysql.connectorDB_HOSTlocalhost DB_PORT3306 DB_NAMElover DB_USERroot DB_PASSWORDxxxx# 连接数据库 connection mysql.connector.connect(hostDB_HOST,portDB_PORT,databaseDB_NAME,userDB_USER,passwordDB_PASSWORD)if connection.is_c…