hid.m 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149
  1. /*
  2. Simple DirectMedia Layer
  3. Copyright (C) 2021 Valve Corporation
  4. This software is provided 'as-is', without any express or implied
  5. warranty. In no event will the authors be held liable for any damages
  6. arising from the use of this software.
  7. Permission is granted to anyone to use this software for any purpose,
  8. including commercial applications, and to alter it and redistribute it
  9. freely, subject to the following restrictions:
  10. 1. The origin of this software must not be misrepresented; you must not
  11. claim that you wrote the original software. If you use this software
  12. in a product, an acknowledgment in the product documentation would be
  13. appreciated but is not required.
  14. 2. Altered source versions must be plainly marked as such, and must not be
  15. misrepresented as being the original software.
  16. 3. This notice may not be removed or altered from any source distribution.
  17. */
  18. #include "SDL_internal.h"
  19. #if defined(SDL_PLATFORM_IOS) || defined(SDL_PLATFORM_TVOS)
  20. #ifndef SDL_HIDAPI_DISABLED
  21. #include "../SDL_hidapi_c.h"
  22. #define hid_close PLATFORM_hid_close
  23. #define hid_device PLATFORM_hid_device
  24. #define hid_device_ PLATFORM_hid_device_
  25. #define hid_enumerate PLATFORM_hid_enumerate
  26. #define hid_error PLATFORM_hid_error
  27. #define hid_exit PLATFORM_hid_exit
  28. #define hid_free_enumeration PLATFORM_hid_free_enumeration
  29. #define hid_get_device_info PLATFORM_hid_get_device_info
  30. #define hid_get_feature_report PLATFORM_hid_get_feature_report
  31. #define hid_get_indexed_string PLATFORM_hid_get_indexed_string
  32. #define hid_get_input_report PLATFORM_hid_get_input_report
  33. #define hid_get_manufacturer_string PLATFORM_hid_get_manufacturer_string
  34. #define hid_get_product_string PLATFORM_hid_get_product_string
  35. #define hid_get_report_descriptor PLATFORM_hid_get_report_descriptor
  36. #define hid_get_serial_number_string PLATFORM_hid_get_serial_number_string
  37. #define hid_init PLATFORM_hid_init
  38. #define hid_open_path PLATFORM_hid_open_path
  39. #define hid_open PLATFORM_hid_open
  40. #define hid_read PLATFORM_hid_read
  41. #define hid_read_timeout PLATFORM_hid_read_timeout
  42. #define hid_send_feature_report PLATFORM_hid_send_feature_report
  43. #define hid_set_nonblocking PLATFORM_hid_set_nonblocking
  44. #define hid_version PLATFORM_hid_version
  45. #define hid_version_str PLATFORM_hid_version_str
  46. #define hid_write PLATFORM_hid_write
  47. #include <CoreBluetooth/CoreBluetooth.h>
  48. #include <QuartzCore/QuartzCore.h>
  49. #import <UIKit/UIKit.h>
  50. #import <mach/mach_time.h>
  51. #include <pthread.h>
  52. #include <sys/time.h>
  53. #include <unistd.h>
  54. #include "../hidapi/hidapi.h"
  55. #define VALVE_USB_VID 0x28DE
  56. #define D0G_BLE2_PID 0x1106
  57. #define TRITON_BLE_PID 0x1303
  58. typedef uint32_t uint32;
  59. typedef uint64_t uint64;
  60. // enables detailed NSLog logging of feature reports
  61. #define FEATURE_REPORT_LOGGING 0
  62. #define REPORT_SEGMENT_DATA_FLAG 0x80
  63. #define REPORT_SEGMENT_LAST_FLAG 0x40
  64. #define VALVE_SERVICE @"100F6C32-1735-4313-B402-38567131E5F3"
  65. // (READ/NOTIFICATIONS)
  66. #define VALVE_INPUT_CHAR_0x1106 @"100F6C33-1735-4313-B402-38567131E5F3"
  67. #define VALVE_INPUT_CHAR_0x1303 @"100F6C7A-1735-4313-B402-38567131E5F3"
  68. //  (READ/WRITE)
  69. #define VALVE_REPORT_CHAR @"100F6C34-1735-4313-B402-38567131E5F3"
  70. // TODO: create CBUUID's in __attribute__((constructor)) rather than doing [CBUUID UUIDWithString:...] everywhere
  71. #pragma pack(push,1)
  72. typedef struct
  73. {
  74. uint8_t segmentHeader;
  75. uint8_t featureReportMessageID;
  76. uint8_t length;
  77. uint8_t settingIdentifier;
  78. union {
  79. uint16_t usPayload;
  80. uint32_t uPayload;
  81. uint64_t ulPayload;
  82. uint8_t ucPayload[15];
  83. };
  84. } bluetoothSegment;
  85. typedef struct {
  86. uint8_t id;
  87. bluetoothSegment segment;
  88. } hidFeatureReport;
  89. #pragma pack(pop)
  90. size_t GetBluetoothSegmentSize(bluetoothSegment *segment)
  91. {
  92. return segment->length + 3;
  93. }
  94. #define RingBuffer_nElem 256
  95. typedef struct {
  96. int _first, _last;
  97. int _cbElem;
  98. uint8_t *_data;
  99. pthread_mutex_t accessLock;
  100. } RingBuffer;
  101. static RingBuffer *RingBuffer_alloc( int cbElem )
  102. {
  103. RingBuffer *this = (RingBuffer *)malloc( sizeof(*this) );
  104. if (!this)
  105. {
  106. return NULL;
  107. }
  108. this->_first = -1;
  109. this->_last = 0;
  110. this->_cbElem = cbElem;
  111. this->_data = (uint8_t *)malloc(RingBuffer_nElem * cbElem);
  112. if ( !this->_data )
  113. {
  114. free( this );
  115. return NULL;
  116. }
  117. pthread_mutex_init( &this->accessLock, 0 );
  118. return this;
  119. }
  120. static void RingBuffer_free( RingBuffer *this )
  121. {
  122. if ( this )
  123. {
  124. free( this->_data );
  125. free( this );
  126. }
  127. }
  128. static bool RingBuffer_write( RingBuffer *this, const uint8_t *src )
  129. {
  130. if ( !this )
  131. {
  132. return false;
  133. }
  134. pthread_mutex_lock( &this->accessLock );
  135. memcpy( &this->_data[ this->_last ], src, this->_cbElem );
  136. if ( this->_first == -1 )
  137. {
  138. this->_first = this->_last;
  139. }
  140. this->_last = ( this->_last + this->_cbElem ) % (RingBuffer_nElem * this->_cbElem);
  141. if ( this->_last == this->_first )
  142. {
  143. this->_first = ( this->_first + this->_cbElem ) % (RingBuffer_nElem * this->_cbElem);
  144. pthread_mutex_unlock( &this->accessLock );
  145. return false;
  146. }
  147. pthread_mutex_unlock( &this->accessLock );
  148. return true;
  149. }
  150. static bool RingBuffer_read( RingBuffer *this, uint8_t *dst )
  151. {
  152. if ( !this )
  153. {
  154. return false;
  155. }
  156. pthread_mutex_lock( &this->accessLock );
  157. if ( this->_first == -1 )
  158. {
  159. pthread_mutex_unlock( &this->accessLock );
  160. return false;
  161. }
  162. memcpy( dst, &this->_data[ this->_first ], this->_cbElem );
  163. this->_first = ( this->_first + this->_cbElem ) % (RingBuffer_nElem * this->_cbElem);
  164. if ( this->_first == this->_last )
  165. {
  166. this->_first = -1;
  167. }
  168. pthread_mutex_unlock( &this->accessLock );
  169. return true;
  170. }
  171. #pragma mark HIDBLEDevice Definition
  172. typedef enum
  173. {
  174. BLEDeviceWaitState_None,
  175. BLEDeviceWaitState_Waiting,
  176. BLEDeviceWaitState_Complete,
  177. BLEDeviceWaitState_Error
  178. } BLEDeviceWaitState;
  179. @interface HIDBLEDevice : NSObject <CBPeripheralDelegate>
  180. {
  181. RingBuffer *_inputReports;
  182. NSData *_featureReport;
  183. NSMutableDictionary *_outputReports;
  184. BLEDeviceWaitState _waitStateForReadFeatureReport;
  185. BLEDeviceWaitState _waitStateForWriteFeatureReport;
  186. }
  187. @property (nonatomic, readwrite) uint16_t pid;
  188. @property (nonatomic, readwrite) bool connected;
  189. @property (nonatomic, readwrite) bool ready;
  190. @property (nonatomic, strong) CBPeripheral *bleSteamController;
  191. @property (nonatomic, strong) CBCharacteristic *bleCharacteristicInput;
  192. @property (nonatomic, strong) CBCharacteristic *bleCharacteristicReport;
  193. - (id)initWithPeripheral:(CBPeripheral *)peripheral;
  194. - (void)onDisconnect;
  195. @end
  196. @interface HIDBLEManager : NSObject <CBCentralManagerDelegate>
  197. @property (nonatomic) int nPendingScans;
  198. @property (nonatomic) int nPendingPairs;
  199. @property (nonatomic, strong) CBCentralManager *centralManager;
  200. @property (nonatomic, strong) NSMapTable<CBPeripheral *, HIDBLEDevice *> *deviceMap;
  201. @property (nonatomic, retain) dispatch_queue_t bleSerialQueue;
  202. + (instancetype)sharedInstance;
  203. - (void)startScan:(int)duration;
  204. - (void)stopScan;
  205. - (int)updateConnectedSteamControllers:(BOOL) bForce;
  206. - (void)appWillResignActiveNotification:(NSNotification *)note;
  207. - (void)appDidBecomeActiveNotification:(NSNotification *)note;
  208. @end
  209. // singleton class - access using HIDBLEManager.sharedInstance
  210. @implementation HIDBLEManager
  211. + (instancetype)sharedInstance
  212. {
  213. static HIDBLEManager *sharedInstance = nil;
  214. static dispatch_once_t onceToken;
  215. dispatch_once(&onceToken, ^{
  216. sharedInstance = [HIDBLEManager new];
  217. sharedInstance.nPendingScans = 0;
  218. sharedInstance.nPendingPairs = 0;
  219. // Bluetooth is currently only used for Steam Controllers, so check that hint
  220. // before initializing Bluetooth, which will prompt the user for permission.
  221. if ( SDL_GetHintBoolean( SDL_HINT_JOYSTICK_HIDAPI_STEAM, false ) )
  222. {
  223. [[NSNotificationCenter defaultCenter] addObserver:sharedInstance selector:@selector(appWillResignActiveNotification:) name: UIApplicationWillResignActiveNotification object:nil];
  224. [[NSNotificationCenter defaultCenter] addObserver:sharedInstance selector:@selector(appDidBecomeActiveNotification:) name:UIApplicationDidBecomeActiveNotification object:nil];
  225. // receive reports on a high-priority serial-queue. optionally put writes on the serial queue to avoid logical
  226. // race conditions talking to the controller from multiple threads, although BLE fragmentation/assembly means
  227. // that we can still screw this up.
  228. // most importantly we need to consume reports at a high priority to avoid the OS thinking we aren't really
  229. // listening to the BLE device, as iOS on slower devices may stop delivery of packets to the app WITHOUT ACTUALLY
  230. // DISCONNECTING FROM THE DEVICE if we don't react quickly enough to their delivery.
  231. // see also the error-handling states in the peripheral delegate to re-open the device if it gets closed
  232. sharedInstance.bleSerialQueue = dispatch_queue_create( "com.valvesoftware.steamcontroller.ble", DISPATCH_QUEUE_SERIAL );
  233. dispatch_set_target_queue( sharedInstance.bleSerialQueue, dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_HIGH, 0 ) );
  234. // creating a CBCentralManager will always trigger a future centralManagerDidUpdateState:
  235. // where any scanning gets started or connecting to existing peripherals happens, it's never already in a
  236. // powered-on state for a newly launched application.
  237. sharedInstance.centralManager = [[CBCentralManager alloc] initWithDelegate:sharedInstance queue:sharedInstance.bleSerialQueue];
  238. }
  239. sharedInstance.deviceMap = [[NSMapTable alloc] initWithKeyOptions:NSMapTableWeakMemory valueOptions:NSMapTableStrongMemory capacity:4];
  240. });
  241. return sharedInstance;
  242. }
  243. // called for NSNotification UIApplicationWillResignActiveNotification
  244. - (void)appWillResignActiveNotification:(NSNotification *)note
  245. {
  246. // we'll get resign-active notification if pairing is happening.
  247. if ( self.nPendingPairs > 0 )
  248. return;
  249. for ( CBPeripheral *peripheral in self.deviceMap )
  250. {
  251. HIDBLEDevice *steamController = [self.deviceMap objectForKey:peripheral];
  252. if ( steamController )
  253. {
  254. [steamController onDisconnect];
  255. [self.centralManager cancelPeripheralConnection:peripheral];
  256. }
  257. }
  258. [self.deviceMap removeAllObjects];
  259. }
  260. // called for NSNotification UIApplicationDidBecomeActiveNotification
  261. // whenever the application comes back from being inactive, trigger a 20s pairing scan and reconnect
  262. // any devices that may have paired while we were inactive.
  263. - (void)appDidBecomeActiveNotification:(NSNotification *)note
  264. {
  265. [self updateConnectedSteamControllers:true];
  266. [self startScan:20];
  267. }
  268. - (int)updateConnectedSteamControllers:(BOOL) bForce
  269. {
  270. static uint64_t s_unLastUpdateTick = 0;
  271. static mach_timebase_info_data_t s_timebase_info;
  272. if ( self.centralManager == nil )
  273. {
  274. return 0;
  275. }
  276. if (s_timebase_info.denom == 0)
  277. {
  278. mach_timebase_info( &s_timebase_info );
  279. }
  280. uint64_t ticksNow = mach_approximate_time();
  281. if ( !bForce && ( ( (ticksNow - s_unLastUpdateTick) * s_timebase_info.numer ) / s_timebase_info.denom ) < (5ull * NSEC_PER_SEC) )
  282. return (int)self.deviceMap.count;
  283. // we can see previously connected BLE peripherals but can't connect until the CBCentralManager
  284. // is fully powered up - only do work when we are in that state
  285. if ( self.centralManager.state != CBManagerStatePoweredOn )
  286. return (int)self.deviceMap.count;
  287. // only update our last-check-time if we actually did work, otherwise there can be a long delay during initial power-up
  288. s_unLastUpdateTick = mach_approximate_time();
  289. // if a pair is in-flight, the central manager may still give it back via retrieveConnected... and
  290. // cause the SDL layer to attempt to initialize it while some of its endpoints haven't yet been established
  291. if ( self.nPendingPairs > 0 )
  292. return (int)self.deviceMap.count;
  293. NSArray<CBPeripheral *> *peripherals = [self.centralManager retrieveConnectedPeripheralsWithServices: @[ [CBUUID UUIDWithString:@"180A"]]];
  294. for ( CBPeripheral *peripheral in peripherals )
  295. {
  296. // we already know this peripheral
  297. if ( [self.deviceMap objectForKey: peripheral] != nil )
  298. continue;
  299. NSLog( @"connected peripheral: %@", peripheral );
  300. if ( [peripheral.name hasPrefix:@"Steam"] )
  301. {
  302. self.nPendingPairs += 1;
  303. HIDBLEDevice *steamController = [[HIDBLEDevice alloc] initWithPeripheral:peripheral];
  304. [self.deviceMap setObject:steamController forKey:peripheral];
  305. [self.centralManager connectPeripheral:peripheral options:nil];
  306. }
  307. }
  308. return (int)self.deviceMap.count;
  309. }
  310. // manual API for folks to start & stop scanning
  311. - (void)startScan:(int)duration
  312. {
  313. if ( self.centralManager == nil )
  314. {
  315. return;
  316. }
  317. NSLog( @"BLE: requesting scan for %d seconds", duration );
  318. @synchronized (self)
  319. {
  320. if ( _nPendingScans++ == 0 )
  321. {
  322. [self.centralManager scanForPeripheralsWithServices:nil options:nil];
  323. }
  324. }
  325. if ( duration != 0 )
  326. {
  327. dispatch_after( dispatch_time( DISPATCH_TIME_NOW, (int64_t)(duration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  328. [self stopScan];
  329. });
  330. }
  331. }
  332. - (void)stopScan
  333. {
  334. if ( self.centralManager == nil )
  335. {
  336. return;
  337. }
  338. NSLog( @"BLE: stopping scan" );
  339. @synchronized (self)
  340. {
  341. if ( --_nPendingScans <= 0 )
  342. {
  343. _nPendingScans = 0;
  344. [self.centralManager stopScan];
  345. }
  346. }
  347. }
  348. #pragma mark CBCentralManagerDelegate Implementation
  349. // called whenever the BLE hardware state changes.
  350. - (void)centralManagerDidUpdateState:(CBCentralManager *)central
  351. {
  352. switch ( central.state )
  353. {
  354. case CBManagerStatePoweredOn:
  355. {
  356. NSLog( @"CoreBluetooth BLE hardware is powered on and ready" );
  357. // at startup, if we have no already attached peripherals, do a 20s scan for new unpaired devices,
  358. // otherwise callers should occasionally do additional scans. we don't want to continuously be
  359. // scanning because it drains battery, causes other nearby people to have a hard time pairing their
  360. // Steam Controllers, and may also trigger firmware weirdness when a device attempts to start
  361. // the pairing sequence multiple times concurrently
  362. if ( [self updateConnectedSteamControllers:false] == 0 )
  363. {
  364. // TODO: we could limit our scan to only peripherals supporting the SteamController service, but
  365. // that service doesn't currently fit in the base advertising packet, we'd need to put it into an
  366. // extended scan packet. Useful optimization downstream, but not currently necessary
  367. // NSArray *services = @[[CBUUID UUIDWithString:VALVE_SERVICE]];
  368. [self startScan:20];
  369. }
  370. break;
  371. }
  372. case CBManagerStatePoweredOff:
  373. NSLog( @"CoreBluetooth BLE hardware is powered off" );
  374. break;
  375. case CBManagerStateUnauthorized:
  376. NSLog( @"CoreBluetooth BLE state is unauthorized" );
  377. break;
  378. case CBManagerStateUnknown:
  379. NSLog( @"CoreBluetooth BLE state is unknown" );
  380. break;
  381. case CBManagerStateUnsupported:
  382. NSLog( @"CoreBluetooth BLE hardware is unsupported on this platform" );
  383. break;
  384. case CBManagerStateResetting:
  385. NSLog( @"CoreBluetooth BLE manager is resetting" );
  386. break;
  387. }
  388. }
  389. - (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
  390. {
  391. HIDBLEDevice *steamController = [_deviceMap objectForKey:peripheral];
  392. steamController.connected = YES;
  393. self.nPendingPairs -= 1;
  394. }
  395. - (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
  396. {
  397. NSLog( @"Failed to connect: %@", error );
  398. [_deviceMap removeObjectForKey:peripheral];
  399. self.nPendingPairs -= 1;
  400. }
  401. - (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
  402. {
  403. NSString *localName = [advertisementData objectForKey:CBAdvertisementDataLocalNameKey];
  404. NSString *log = [NSString stringWithFormat:@"Found '%@'", localName];
  405. if ( [localName hasPrefix:@"Steam"] )
  406. {
  407. NSLog( @"%@ : %@ - %@", log, peripheral, advertisementData );
  408. self.nPendingPairs += 1;
  409. HIDBLEDevice *steamController = [[HIDBLEDevice alloc] initWithPeripheral:peripheral];
  410. [self.deviceMap setObject:steamController forKey:peripheral];
  411. [self.centralManager connectPeripheral:peripheral options:nil];
  412. }
  413. }
  414. - (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
  415. {
  416. HIDBLEDevice *steamController = [self.deviceMap objectForKey:peripheral];
  417. if ( steamController )
  418. {
  419. [steamController onDisconnect];
  420. [self.deviceMap removeObjectForKey:peripheral];
  421. }
  422. }
  423. @end
  424. // Core Bluetooth devices calling back on event boundaries of their run-loops. so annoying.
  425. static void process_pending_events(void)
  426. {
  427. CFRunLoopRunResult res;
  428. do
  429. {
  430. res = CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0.001, FALSE );
  431. }
  432. while( res != kCFRunLoopRunFinished && res != kCFRunLoopRunTimedOut );
  433. }
  434. @implementation HIDBLEDevice
  435. - (id)init
  436. {
  437. if ( self = [super init] )
  438. {
  439. self.pid = 0;
  440. _inputReports = NULL;
  441. _outputReports = [[NSMutableDictionary alloc] init];
  442. _connected = NO;
  443. _ready = NO;
  444. self.bleSteamController = nil;
  445. self.bleCharacteristicInput = nil;
  446. self.bleCharacteristicReport = nil;
  447. }
  448. return self;
  449. }
  450. - (id)initWithPeripheral:(CBPeripheral *)peripheral
  451. {
  452. if ( self = [super init] )
  453. {
  454. self.pid = 0;
  455. _inputReports = NULL;
  456. _outputReports = [[NSMutableDictionary alloc] init];
  457. _connected = NO;
  458. _ready = NO;
  459. self.bleSteamController = peripheral;
  460. if ( peripheral )
  461. {
  462. peripheral.delegate = self;
  463. }
  464. self.bleCharacteristicInput = nil;
  465. self.bleCharacteristicReport = nil;
  466. }
  467. return self;
  468. }
  469. - (void)onDisconnect
  470. {
  471. self.connected = NO;
  472. self.ready = NO;
  473. if ( _inputReports )
  474. {
  475. RingBuffer_free( _inputReports );
  476. _inputReports = NULL;
  477. }
  478. }
  479. - (void)setConnected:(bool)connected
  480. {
  481. _connected = connected;
  482. if ( _connected )
  483. {
  484. [_bleSteamController discoverServices:nil];
  485. }
  486. else
  487. {
  488. NSLog( @"Disconnected" );
  489. }
  490. }
  491. - (size_t)read_input_report:(uint8_t *)dst
  492. {
  493. if ( RingBuffer_read( _inputReports, dst+1 ) )
  494. {
  495. switch ( self.pid )
  496. {
  497. case D0G_BLE2_PID:
  498. *dst = 0x03;
  499. break;
  500. case TRITON_BLE_PID:
  501. *dst = 0x45;
  502. break;
  503. default:
  504. abort();
  505. }
  506. return _inputReports->_cbElem + 1;
  507. }
  508. return 0;
  509. }
  510. - (int)send_report:(const uint8_t *)data length:(size_t)length
  511. {
  512. if ( self.pid == D0G_BLE2_PID )
  513. {
  514. [_bleSteamController writeValue:[NSData dataWithBytes:data length:length] forCharacteristic:_bleCharacteristicReport type:CBCharacteristicWriteWithResponse];
  515. return (int)length;
  516. }
  517. // We need to look up the correct characteristic for this output report
  518. if ( length > 0 )
  519. {
  520. CBCharacteristic *aChar = [_outputReports objectForKey:[NSNumber numberWithInt:data[0]]];
  521. if ( aChar != nil )
  522. {
  523. [_bleSteamController writeValue:[NSData dataWithBytes:&data[1] length:(length - 1)] forCharacteristic:aChar type:CBCharacteristicWriteWithResponse];
  524. return (int)length;
  525. }
  526. }
  527. return -1;
  528. }
  529. - (int)send_feature_report:(hidFeatureReport *)report length:(size_t)length
  530. {
  531. #if FEATURE_REPORT_LOGGING
  532. uint8_t *reportBytes = (uint8_t *)report;
  533. NSLog( @"HIDBLE:send_feature_report (%02zu/19) [%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x]", length,
  534. reportBytes[1], reportBytes[2], reportBytes[3], reportBytes[4], reportBytes[5], reportBytes[6],
  535. reportBytes[7], reportBytes[8], reportBytes[9], reportBytes[10], reportBytes[11], reportBytes[12],
  536. reportBytes[13], reportBytes[14], reportBytes[15], reportBytes[16], reportBytes[17], reportBytes[18],
  537. reportBytes[19] );
  538. #endif
  539. #if 1
  540. // fire-and-forget - we are going to not wait for the response here because all Steam Controller BLE send_feature_report's are ignored,
  541. // except errors.
  542. [_bleSteamController writeValue:[NSData dataWithBytes:&report->segment length:MIN(length, 64)] forCharacteristic:_bleCharacteristicReport type:CBCharacteristicWriteWithResponse];
  543. // pretend we received a result anybody cares about
  544. return (int)length;
  545. #else
  546. // this is technically the correct send_feature_report logic if you want to make sure it gets through and is
  547. // acknowledged or errors out
  548. _waitStateForWriteFeatureReport = BLEDeviceWaitState_Waiting;
  549. [_bleSteamController writeValue:[NSData dataWithBytes:&report->segment length:MIN(length, 64)
  550. ] forCharacteristic:_bleCharacteristicReport type:CBCharacteristicWriteWithResponse];
  551. while ( _connected && _waitStateForWriteFeatureReport == BLEDeviceWaitState_Waiting )
  552. {
  553. process_pending_events();
  554. }
  555. if ( !_connected || _waitStateForWriteFeatureReport == BLEDeviceWaitState_Error )
  556. {
  557. _waitStateForWriteFeatureReport = BLEDeviceWaitState_None;
  558. return -1;
  559. }
  560. _waitStateForWriteFeatureReport = BLEDeviceWaitState_None;
  561. return (int)length;
  562. #endif
  563. }
  564. - (int)get_feature_report:(uint8_t)feature into:(uint8_t *)buffer length:(size_t)length
  565. {
  566. _waitStateForReadFeatureReport = BLEDeviceWaitState_Waiting;
  567. [_bleSteamController readValueForCharacteristic:_bleCharacteristicReport];
  568. while ( _connected && _waitStateForReadFeatureReport == BLEDeviceWaitState_Waiting )
  569. {
  570. process_pending_events();
  571. }
  572. if ( !_connected || _waitStateForReadFeatureReport == BLEDeviceWaitState_Error )
  573. {
  574. _waitStateForReadFeatureReport = BLEDeviceWaitState_None;
  575. return -1;
  576. }
  577. int amount = 0;
  578. if ( _featureReport.length > 0 )
  579. {
  580. uint8_t *data = (uint8_t *)_featureReport.bytes;
  581. if ( *data == *buffer )
  582. {
  583. amount = (int)MIN( length, _featureReport.length );
  584. memcpy( buffer, _featureReport.bytes, amount );
  585. }
  586. else
  587. {
  588. // Leave the report in the buffer
  589. amount = (int)MIN( length - 1, _featureReport.length );
  590. memcpy( &buffer[ 1 ], _featureReport.bytes, amount );
  591. ++amount;
  592. }
  593. }
  594. _waitStateForReadFeatureReport = BLEDeviceWaitState_None;
  595. #if FEATURE_REPORT_LOGGING
  596. NSLog( @"HIDBLE:get_feature_report (%lu/%zu) [%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x]",
  597. _featureReport.length, length,
  598. buffer[1], buffer[2], buffer[3], buffer[4], buffer[5], buffer[6],
  599. buffer[7], buffer[8], buffer[9], buffer[10], buffer[11], buffer[12],
  600. buffer[13], buffer[14], buffer[15], buffer[16], buffer[17], buffer[18],
  601. buffer[19] );
  602. #endif
  603. return amount;
  604. }
  605. #pragma mark CBPeripheralDelegate Implementation
  606. - (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error
  607. {
  608. for (CBService *service in peripheral.services)
  609. {
  610. NSLog( @"Found Service: %@", service );
  611. if ( [service.UUID isEqual:[CBUUID UUIDWithString:VALVE_SERVICE]] )
  612. {
  613. [peripheral discoverCharacteristics:nil forService:service];
  614. }
  615. }
  616. }
  617. - (void)peripheral:(CBPeripheral *)peripheral didDiscoverDescriptorsForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
  618. {
  619. // nothing yet needed here, enable for logging
  620. if ( /* DISABLES CODE */ (0) )
  621. {
  622. for ( CBDescriptor *descriptor in characteristic.descriptors )
  623. {
  624. NSLog( @" - Descriptor '%@'", descriptor );
  625. }
  626. }
  627. }
  628. - (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
  629. {
  630. if ([service.UUID isEqual:[CBUUID UUIDWithString:VALVE_SERVICE]])
  631. {
  632. for (CBCharacteristic *aChar in service.characteristics)
  633. {
  634. NSLog( @"Found Characteristic %@", aChar );
  635. if ( [aChar.UUID isEqual:[CBUUID UUIDWithString:VALVE_INPUT_CHAR_0x1106]] )
  636. {
  637. self.pid = D0G_BLE2_PID;
  638. self.bleCharacteristicInput = aChar;
  639. }
  640. else if ( [aChar.UUID isEqual:[CBUUID UUIDWithString:VALVE_INPUT_CHAR_0x1303]] )
  641. {
  642. self.pid = TRITON_BLE_PID;
  643. self.bleCharacteristicInput = aChar;
  644. }
  645. else if ( [aChar.UUID isEqual:[CBUUID UUIDWithString:VALVE_REPORT_CHAR]] )
  646. {
  647. self.bleCharacteristicReport = aChar;
  648. [self.bleSteamController discoverDescriptorsForCharacteristic: aChar];
  649. }
  650. else
  651. {
  652. NSString *UUIDString = [aChar.UUID UUIDString];
  653. int report_id = 0;
  654. if ( sscanf( UUIDString.UTF8String, "100F6C%x", &report_id ) == 1 && report_id > 0x35 )
  655. {
  656. report_id -= 0x35;
  657. //NSLog( @"Found characteristic for output report 0x%.2x", report_id );
  658. if (report_id >= 0x80) {
  659. // An output report
  660. [_outputReports setObject:aChar forKey:[NSNumber numberWithInt:report_id]];
  661. }
  662. }
  663. }
  664. }
  665. }
  666. }
  667. - (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
  668. {
  669. static uint64_t s_ticksLastOverflowReport = 0;
  670. // receiving an input report is the final indicator that the user accepted a pairing
  671. // request and that we successfully established notification. CoreBluetooth has no
  672. // notification of the pairing acknowledgement, which is a bad oversight.
  673. if ( self.ready == NO )
  674. {
  675. self.ready = YES;
  676. if ( _inputReports == NULL )
  677. {
  678. int cbElem = 0;
  679. switch ( self.pid )
  680. {
  681. case D0G_BLE2_PID:
  682. cbElem = 19;
  683. break;
  684. case TRITON_BLE_PID:
  685. cbElem = 45;
  686. break;
  687. default:
  688. abort();
  689. }
  690. _inputReports = RingBuffer_alloc( cbElem );
  691. }
  692. HIDBLEManager.sharedInstance.nPendingPairs -= 1;
  693. }
  694. if ( [characteristic.UUID isEqual:_bleCharacteristicInput.UUID] )
  695. {
  696. NSData *data = [characteristic value];
  697. if ( _inputReports && data.length != _inputReports->_cbElem )
  698. {
  699. NSLog( @"HIDBLE: incoming data is %lu bytes should be exactly %d", (unsigned long)data.length, _inputReports->_cbElem );
  700. }
  701. if ( !RingBuffer_write( _inputReports, (const uint8_t *)data.bytes ) )
  702. {
  703. uint64_t ticksNow = mach_approximate_time();
  704. if ( ticksNow - s_ticksLastOverflowReport > (5ull * NSEC_PER_SEC / 10) )
  705. {
  706. NSLog( @"HIDBLE: input report buffer overflow" );
  707. s_ticksLastOverflowReport = ticksNow;
  708. }
  709. }
  710. }
  711. else if ( [characteristic.UUID isEqual:_bleCharacteristicReport.UUID] )
  712. {
  713. if ( error != nil )
  714. {
  715. NSLog( @"HIDBLE: get_feature_report error: %@", error );
  716. _waitStateForReadFeatureReport = BLEDeviceWaitState_Error;
  717. }
  718. else
  719. {
  720. _featureReport = [characteristic value];
  721. _waitStateForReadFeatureReport = BLEDeviceWaitState_Complete;
  722. }
  723. }
  724. }
  725. - (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
  726. {
  727. if ( [characteristic.UUID isEqual:[CBUUID UUIDWithString:VALVE_REPORT_CHAR]] )
  728. {
  729. if ( error != nil )
  730. {
  731. NSLog( @"HIDBLE: write_feature_report error: %@", error );
  732. _waitStateForWriteFeatureReport = BLEDeviceWaitState_Error;
  733. }
  734. else
  735. {
  736. _waitStateForWriteFeatureReport = BLEDeviceWaitState_Complete;
  737. }
  738. }
  739. }
  740. - (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
  741. {
  742. //NSLog( @"didUpdateNotificationStateForCharacteristic %@ (%@)", characteristic, error );
  743. }
  744. @end
  745. #pragma mark hid_api implementation
  746. struct hid_device_ {
  747. void *device_handle;
  748. int blocking;
  749. struct hid_device_info* device_info;
  750. hid_device *next;
  751. };
  752. int HID_API_EXPORT HID_API_CALL hid_init(void)
  753. {
  754. return ( HIDBLEManager.sharedInstance == nil ) ? -1 : 0;
  755. }
  756. int HID_API_EXPORT HID_API_CALL hid_exit(void)
  757. {
  758. return 0;
  759. }
  760. void HID_API_EXPORT HID_API_CALL hid_ble_scan( int bStart )
  761. {
  762. HIDBLEManager *bleManager = HIDBLEManager.sharedInstance;
  763. if ( bStart )
  764. {
  765. [bleManager startScan:0];
  766. }
  767. else
  768. {
  769. [bleManager stopScan];
  770. }
  771. }
  772. HID_API_EXPORT hid_device * HID_API_CALL hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number)
  773. {
  774. return NULL;
  775. }
  776. HID_API_EXPORT hid_device * HID_API_CALL hid_open_path( const char *path )
  777. {
  778. hid_device *result = NULL;
  779. NSString *nssPath = [NSString stringWithUTF8String:path];
  780. HIDBLEManager *bleManager = HIDBLEManager.sharedInstance;
  781. NSEnumerator<HIDBLEDevice *> *devices = [bleManager.deviceMap objectEnumerator];
  782. for ( HIDBLEDevice *device in devices )
  783. {
  784. // we have the device but it hasn't found its service or characteristics until it is connected
  785. if ( !device.ready || !device.connected || !device.bleCharacteristicInput )
  786. continue;
  787. if ( [device.bleSteamController.identifier.UUIDString isEqualToString:nssPath] )
  788. {
  789. result = (hid_device *)malloc( sizeof( hid_device ) );
  790. memset( result, 0, sizeof( hid_device ) );
  791. result->device_handle = (void*)CFBridgingRetain( device );
  792. result->blocking = NO;
  793. // enable reporting input events on the characteristic
  794. [device.bleSteamController setNotifyValue:YES forCharacteristic:device.bleCharacteristicInput];
  795. return result;
  796. }
  797. }
  798. return result;
  799. }
  800. void HID_API_EXPORT hid_free_enumeration(struct hid_device_info *devs)
  801. {
  802. /* This function is identical to the Linux version. Platform independent. */
  803. struct hid_device_info *d = devs;
  804. while (d) {
  805. struct hid_device_info *next = d->next;
  806. free(d->path);
  807. free(d->serial_number);
  808. free(d->manufacturer_string);
  809. free(d->product_string);
  810. free(d);
  811. d = next;
  812. }
  813. }
  814. int HID_API_EXPORT hid_set_nonblocking(hid_device *dev, int nonblock)
  815. {
  816. /* All Nonblocking operation is handled by the library. */
  817. dev->blocking = !nonblock;
  818. return 0;
  819. }
  820. static struct hid_device_info *create_device_info_for_hid_device(HIDBLEDevice *device)
  821. {
  822. // We currently only support the Steam Controller
  823. struct hid_device_info *device_info = (struct hid_device_info *)malloc( sizeof(struct hid_device_info) );
  824. memset( device_info, 0, sizeof(struct hid_device_info) );
  825. device_info->path = strdup( device.bleSteamController.identifier.UUIDString.UTF8String );
  826. device_info->vendor_id = VALVE_USB_VID;
  827. device_info->product_id = device.pid;
  828. device_info->product_string = wcsdup( L"Steam Controller" );
  829. device_info->manufacturer_string = wcsdup( L"Valve Corporation" );
  830. device_info->bus_type = HID_API_BUS_BLUETOOTH;
  831. return device_info;
  832. }
  833. struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, unsigned short product_id)
  834. { @autoreleasepool {
  835. struct hid_device_info *root = NULL;
  836. HIDBLEManager *bleManager = HIDBLEManager.sharedInstance;
  837. [bleManager updateConnectedSteamControllers:false];
  838. NSEnumerator<HIDBLEDevice *> *devices = [bleManager.deviceMap objectEnumerator];
  839. for ( HIDBLEDevice *device in devices )
  840. {
  841. // there are several brief windows in connecting to an already paired device and
  842. // one long window waiting for users to confirm pairing where we don't want
  843. // to consider a device ready - if we hand it back to SDL or another
  844. // Steam Controller consumer, their additional SC setup work will fail
  845. // in unusual/silent ways and we can actually corrupt the BLE stack for
  846. // the entire system and kill the appletv remote's Menu button (!)
  847. if ( device.bleSteamController.state != CBPeripheralStateConnected ||
  848. device.connected == NO || device.ready == NO )
  849. {
  850. if ( device.ready == NO && device.bleCharacteristicInput != nil )
  851. {
  852. // attempt to register for input reports. this call will silently fail
  853. // until the pairing finalizes with user acceptance. oh, apple.
  854. [device.bleSteamController setNotifyValue:YES forCharacteristic:device.bleCharacteristicInput];
  855. }
  856. continue;
  857. }
  858. if ( ( vendor_id != 0 && vendor_id != VALVE_USB_VID ) ||
  859. ( product_id != 0 && product_id != device.pid ) )
  860. {
  861. continue;
  862. }
  863. /* See if there are any devices we should skip in enumeration */
  864. if (SDL_HIDAPI_ShouldIgnoreDevice(HID_API_BUS_BLUETOOTH, VALVE_USB_VID, device.pid, 0, 0, false)) {
  865. continue;
  866. }
  867. struct hid_device_info *device_info = create_device_info_for_hid_device(device);
  868. device_info->next = root;
  869. root = device_info;
  870. }
  871. return root;
  872. }}
  873. int HID_API_EXPORT_CALL hid_get_manufacturer_string(hid_device *dev, wchar_t *string, size_t maxlen)
  874. {
  875. static wchar_t s_wszManufacturer[] = L"Valve Corporation";
  876. wcsncpy( string, s_wszManufacturer, sizeof(s_wszManufacturer)/sizeof(s_wszManufacturer[0]) );
  877. return 0;
  878. }
  879. int HID_API_EXPORT_CALL hid_get_product_string(hid_device *dev, wchar_t *string, size_t maxlen)
  880. {
  881. static wchar_t s_wszProduct[] = L"Steam Controller";
  882. wcsncpy( string, s_wszProduct, sizeof(s_wszProduct)/sizeof(s_wszProduct[0]) );
  883. return 0;
  884. }
  885. int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen)
  886. {
  887. static wchar_t s_wszSerial[] = L"12345";
  888. wcsncpy( string, s_wszSerial, sizeof(s_wszSerial)/sizeof(s_wszSerial[0]) );
  889. return 0;
  890. }
  891. int HID_API_EXPORT_CALL hid_get_indexed_string(hid_device *dev, int string_index, wchar_t *string, size_t maxlen)
  892. {
  893. return -1;
  894. }
  895. struct hid_device_info *hid_get_device_info(hid_device *dev)
  896. {
  897. HIDBLEDevice *device_handle = (__bridge HIDBLEDevice *)dev->device_handle;
  898. if (!dev->device_info) {
  899. // Lazy initialize device_info
  900. dev->device_info = create_device_info_for_hid_device(device_handle);
  901. }
  902. // create_device_info_for_hid_device will set an error if needed
  903. return dev->device_info;
  904. }
  905. int hid_get_report_descriptor(hid_device *device, unsigned char *buf, size_t buf_size)
  906. {
  907. // Not implemented
  908. return -1;
  909. }
  910. int HID_API_EXPORT hid_write(hid_device *dev, const unsigned char *data, size_t length)
  911. {
  912. HIDBLEDevice *device_handle = (__bridge HIDBLEDevice *)dev->device_handle;
  913. if ( !device_handle.connected )
  914. return -1;
  915. return [device_handle send_report:data length:length];
  916. }
  917. void HID_API_EXPORT hid_close(hid_device *dev)
  918. {
  919. HIDBLEDevice *device_handle = CFBridgingRelease( dev->device_handle );
  920. // disable reporting input events on the characteristic
  921. if ( device_handle.connected ) {
  922. [device_handle.bleSteamController setNotifyValue:NO forCharacteristic:device_handle.bleCharacteristicInput];
  923. }
  924. hid_free_enumeration(dev->device_info);
  925. free( dev );
  926. }
  927. int HID_API_EXPORT hid_send_feature_report(hid_device *dev, const unsigned char *data, size_t length)
  928. {
  929. HIDBLEDevice *device_handle = (__bridge HIDBLEDevice *)dev->device_handle;
  930. if ( !device_handle.connected )
  931. return -1;
  932. return [device_handle send_feature_report:(hidFeatureReport *)(void *)data length:length];
  933. }
  934. int HID_API_EXPORT hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length)
  935. {
  936. HIDBLEDevice *device_handle = (__bridge HIDBLEDevice *)dev->device_handle;
  937. if ( !device_handle.connected )
  938. return -1;
  939. size_t written = [device_handle get_feature_report:data[0] into:data length:length];
  940. return written == length-1 ? (int)length : (int)written;
  941. }
  942. int HID_API_EXPORT hid_get_input_report(hid_device *dev, unsigned char *data, size_t length)
  943. {
  944. // Not supported
  945. return -1;
  946. }
  947. int HID_API_EXPORT hid_read(hid_device *dev, unsigned char *data, size_t length)
  948. {
  949. HIDBLEDevice *device_handle = (__bridge HIDBLEDevice *)dev->device_handle;
  950. if ( !device_handle.connected )
  951. return -1;
  952. return hid_read_timeout(dev, data, length, 0);
  953. }
  954. int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t length, int milliseconds)
  955. {
  956. HIDBLEDevice *device_handle = (__bridge HIDBLEDevice *)dev->device_handle;
  957. if ( !device_handle.connected )
  958. return -1;
  959. if ( milliseconds != 0 )
  960. {
  961. NSLog( @"hid_read_timeout with non-zero wait" );
  962. }
  963. int result = (int)[device_handle read_input_report:data];
  964. #if 0 //FEATURE_REPORT_LOGGING
  965. NSLog( @"HIDBLE:hid_read_timeout (%d) [%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x]", result,
  966. data[1], data[2], data[3], data[4], data[5], data[6],
  967. data[7], data[8], data[9], data[10], data[11], data[12],
  968. data[13], data[14], data[15], data[16], data[17], data[18],
  969. data[19] );
  970. #endif
  971. return result;
  972. }
  973. HID_API_EXPORT const wchar_t* HID_API_CALL hid_error(hid_device *dev)
  974. {
  975. return NULL;
  976. }
  977. #endif /* !SDL_HIDAPI_DISABLED */
  978. #endif /* SDL_PLATFORM_IOS || SDL_PLATFORM_TVOS */