hid.m 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915
  1. //======== Copyright (c) 2017 Valve Corporation, All rights reserved. =========
  2. //
  3. // Purpose: HID device abstraction temporary stub
  4. //
  5. //=============================================================================
  6. #include "../../SDL_internal.h"
  7. #ifdef SDL_JOYSTICK_HIDAPI
  8. #include <CoreBluetooth/CoreBluetooth.h>
  9. #include <QuartzCore/QuartzCore.h>
  10. #import <UIKit/UIKit.h>
  11. #import <mach/mach_time.h>
  12. #include <pthread.h>
  13. #include <sys/time.h>
  14. #include <unistd.h>
  15. #include "../hidapi/hidapi.h"
  16. #define VALVE_USB_VID 0x28DE
  17. #define D0G_BLE2_PID 0x1106
  18. typedef uint32_t uint32;
  19. typedef uint64_t uint64;
  20. // enables detailed NSLog logging of feature reports
  21. #define FEATURE_REPORT_LOGGING 0
  22. #define REPORT_SEGMENT_DATA_FLAG 0x80
  23. #define REPORT_SEGMENT_LAST_FLAG 0x40
  24. #define VALVE_SERVICE @"100F6C32-1735-4313-B402-38567131E5F3"
  25. // (READ/NOTIFICATIONS)
  26. #define VALVE_INPUT_CHAR @"100F6C33-1735-4313-B402-38567131E5F3"
  27. //  (READ/WRITE)
  28. #define VALVE_REPORT_CHAR @"100F6C34-1735-4313-B402-38567131E5F3"
  29. // TODO: create CBUUID's in __attribute__((constructor)) rather than doing [CBUUID UUIDWithString:...] everywhere
  30. #pragma pack(push,1)
  31. typedef struct
  32. {
  33. uint8_t segmentHeader;
  34. uint8_t featureReportMessageID;
  35. uint8_t length;
  36. uint8_t settingIdentifier;
  37. union {
  38. uint16_t usPayload;
  39. uint32_t uPayload;
  40. uint64_t ulPayload;
  41. uint8_t ucPayload[15];
  42. };
  43. } bluetoothSegment;
  44. typedef struct {
  45. uint8_t id;
  46. union {
  47. bluetoothSegment segment;
  48. struct {
  49. uint8_t segmentHeader;
  50. uint8_t featureReportMessageID;
  51. uint8_t length;
  52. uint8_t settingIdentifier;
  53. union {
  54. uint16_t usPayload;
  55. uint32_t uPayload;
  56. uint64_t ulPayload;
  57. uint8_t ucPayload[15];
  58. };
  59. };
  60. };
  61. } hidFeatureReport;
  62. #pragma pack(pop)
  63. size_t GetBluetoothSegmentSize(bluetoothSegment *segment)
  64. {
  65. return segment->length + 3;
  66. }
  67. #define RingBuffer_cbElem 19
  68. #define RingBuffer_nElem 4096
  69. typedef struct {
  70. int _first, _last;
  71. uint8_t _data[ ( RingBuffer_nElem * RingBuffer_cbElem ) ];
  72. pthread_mutex_t accessLock;
  73. } RingBuffer;
  74. static void RingBuffer_init( RingBuffer *this )
  75. {
  76. this->_first = -1;
  77. this->_last = 0;
  78. pthread_mutex_init( &this->accessLock, 0 );
  79. }
  80. static bool RingBuffer_write( RingBuffer *this, const uint8_t *src )
  81. {
  82. pthread_mutex_lock( &this->accessLock );
  83. memcpy( &this->_data[ this->_last ], src, RingBuffer_cbElem );
  84. if ( this->_first == -1 )
  85. {
  86. this->_first = this->_last;
  87. }
  88. this->_last = ( this->_last + RingBuffer_cbElem ) % (RingBuffer_nElem * RingBuffer_cbElem);
  89. if ( this->_last == this->_first )
  90. {
  91. this->_first = ( this->_first + RingBuffer_cbElem ) % (RingBuffer_nElem * RingBuffer_cbElem);
  92. pthread_mutex_unlock( &this->accessLock );
  93. return false;
  94. }
  95. pthread_mutex_unlock( &this->accessLock );
  96. return true;
  97. }
  98. static bool RingBuffer_read( RingBuffer *this, uint8_t *dst )
  99. {
  100. pthread_mutex_lock( &this->accessLock );
  101. if ( this->_first == -1 )
  102. {
  103. pthread_mutex_unlock( &this->accessLock );
  104. return false;
  105. }
  106. memcpy( dst, &this->_data[ this->_first ], RingBuffer_cbElem );
  107. this->_first = ( this->_first + RingBuffer_cbElem ) % (RingBuffer_nElem * RingBuffer_cbElem);
  108. if ( this->_first == this->_last )
  109. {
  110. this->_first = -1;
  111. }
  112. pthread_mutex_unlock( &this->accessLock );
  113. return true;
  114. }
  115. #pragma mark HIDBLEDevice Definition
  116. typedef enum
  117. {
  118. BLEDeviceWaitState_None,
  119. BLEDeviceWaitState_Waiting,
  120. BLEDeviceWaitState_Complete,
  121. BLEDeviceWaitState_Error
  122. } BLEDeviceWaitState;
  123. @interface HIDBLEDevice : NSObject <CBPeripheralDelegate>
  124. {
  125. RingBuffer _inputReports;
  126. uint8_t _featureReport[20];
  127. BLEDeviceWaitState _waitStateForReadFeatureReport;
  128. BLEDeviceWaitState _waitStateForWriteFeatureReport;
  129. }
  130. @property (nonatomic, readwrite) bool connected;
  131. @property (nonatomic, readwrite) bool ready;
  132. @property (nonatomic, strong) CBPeripheral *bleSteamController;
  133. @property (nonatomic, strong) CBCharacteristic *bleCharacteristicInput;
  134. @property (nonatomic, strong) CBCharacteristic *bleCharacteristicReport;
  135. - (id)initWithPeripheral:(CBPeripheral *)peripheral;
  136. @end
  137. @interface HIDBLEManager : NSObject <CBCentralManagerDelegate>
  138. @property (nonatomic) int nPendingScans;
  139. @property (nonatomic) int nPendingPairs;
  140. @property (nonatomic, strong) CBCentralManager *centralManager;
  141. @property (nonatomic, strong) NSMapTable<CBPeripheral *, HIDBLEDevice *> *deviceMap;
  142. @property (nonatomic, retain) dispatch_queue_t bleSerialQueue;
  143. + (instancetype)sharedInstance;
  144. - (void)startScan:(int)duration;
  145. - (void)stopScan;
  146. - (int)updateConnectedSteamControllers:(BOOL) bForce;
  147. - (void)appWillResignActiveNotification:(NSNotification *)note;
  148. - (void)appDidBecomeActiveNotification:(NSNotification *)note;
  149. @end
  150. // singleton class - access using HIDBLEManager.sharedInstance
  151. @implementation HIDBLEManager
  152. + (instancetype)sharedInstance
  153. {
  154. static HIDBLEManager *sharedInstance = nil;
  155. static dispatch_once_t onceToken;
  156. dispatch_once(&onceToken, ^{
  157. sharedInstance = [HIDBLEManager new];
  158. sharedInstance.nPendingScans = 0;
  159. sharedInstance.nPendingPairs = 0;
  160. [[NSNotificationCenter defaultCenter] addObserver:sharedInstance selector:@selector(appWillResignActiveNotification:) name: UIApplicationWillResignActiveNotification object:nil];
  161. [[NSNotificationCenter defaultCenter] addObserver:sharedInstance selector:@selector(appDidBecomeActiveNotification:) name:UIApplicationDidBecomeActiveNotification object:nil];
  162. // receive reports on a high-priority serial-queue. optionally put writes on the serial queue to avoid logical
  163. // race conditions talking to the controller from multiple threads, although BLE fragmentation/assembly means
  164. // that we can still screw this up.
  165. // most importantly we need to consume reports at a high priority to avoid the OS thinking we aren't really
  166. // listening to the BLE device, as iOS on slower devices may stop delivery of packets to the app WITHOUT ACTUALLY
  167. // DISCONNECTING FROM THE DEVICE if we don't react quickly enough to their delivery.
  168. // see also the error-handling states in the peripheral delegate to re-open the device if it gets closed
  169. sharedInstance.bleSerialQueue = dispatch_queue_create( "com.valvesoftware.steamcontroller.ble", DISPATCH_QUEUE_SERIAL );
  170. dispatch_set_target_queue( sharedInstance.bleSerialQueue, dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_HIGH, 0 ) );
  171. // creating a CBCentralManager will always trigger a future centralManagerDidUpdateState:
  172. // where any scanning gets started or connecting to existing peripherals happens, it's never already in a
  173. // powered-on state for a newly launched application.
  174. sharedInstance.centralManager = [[CBCentralManager alloc] initWithDelegate:sharedInstance queue:sharedInstance.bleSerialQueue];
  175. sharedInstance.deviceMap = [[NSMapTable alloc] initWithKeyOptions:NSMapTableWeakMemory valueOptions:NSMapTableStrongMemory capacity:4];
  176. });
  177. return sharedInstance;
  178. }
  179. // called for NSNotification UIApplicationWillResignActiveNotification
  180. - (void)appWillResignActiveNotification:(NSNotification *)note
  181. {
  182. // we'll get resign-active notification if pairing is happening.
  183. if ( self.nPendingPairs > 0 )
  184. return;
  185. for ( CBPeripheral *peripheral in self.deviceMap )
  186. {
  187. HIDBLEDevice *steamController = [self.deviceMap objectForKey:peripheral];
  188. if ( steamController )
  189. {
  190. steamController.connected = NO;
  191. steamController.ready = NO;
  192. [self.centralManager cancelPeripheralConnection:peripheral];
  193. }
  194. }
  195. [self.deviceMap removeAllObjects];
  196. }
  197. // called for NSNotification UIApplicationDidBecomeActiveNotification
  198. // whenever the application comes back from being inactive, trigger a 20s pairing scan and reconnect
  199. // any devices that may have paired while we were inactive.
  200. - (void)appDidBecomeActiveNotification:(NSNotification *)note
  201. {
  202. [self updateConnectedSteamControllers:true];
  203. [self startScan:20];
  204. }
  205. - (int)updateConnectedSteamControllers:(BOOL) bForce
  206. {
  207. static uint64_t s_unLastUpdateTick = 0;
  208. static mach_timebase_info_data_t s_timebase_info;
  209. if (s_timebase_info.denom == 0)
  210. {
  211. mach_timebase_info( &s_timebase_info );
  212. }
  213. uint64_t ticksNow = mach_approximate_time();
  214. if ( !bForce && ( ( (ticksNow - s_unLastUpdateTick) * s_timebase_info.numer ) / s_timebase_info.denom ) < (5ull * NSEC_PER_SEC) )
  215. return (int)self.deviceMap.count;
  216. // we can see previously connected BLE peripherals but can't connect until the CBCentralManager
  217. // is fully powered up - only do work when we are in that state
  218. if ( self.centralManager.state != CBManagerStatePoweredOn )
  219. return (int)self.deviceMap.count;
  220. // only update our last-check-time if we actually did work, otherwise there can be a long delay during initial power-up
  221. s_unLastUpdateTick = mach_approximate_time();
  222. // if a pair is in-flight, the central manager may still give it back via retrieveConnected... and
  223. // cause the SDL layer to attempt to initialize it while some of its endpoints haven't yet been established
  224. if ( self.nPendingPairs > 0 )
  225. return (int)self.deviceMap.count;
  226. NSArray<CBPeripheral *> *peripherals = [self.centralManager retrieveConnectedPeripheralsWithServices: @[ [CBUUID UUIDWithString:@"180A"]]];
  227. for ( CBPeripheral *peripheral in peripherals )
  228. {
  229. // we already know this peripheral
  230. if ( [self.deviceMap objectForKey: peripheral] != nil )
  231. continue;
  232. NSLog( @"connected peripheral: %@", peripheral );
  233. if ( [peripheral.name isEqualToString:@"SteamController"] )
  234. {
  235. self.nPendingPairs += 1;
  236. HIDBLEDevice *steamController = [[HIDBLEDevice alloc] initWithPeripheral:peripheral];
  237. [self.deviceMap setObject:steamController forKey:peripheral];
  238. [self.centralManager connectPeripheral:peripheral options:nil];
  239. }
  240. }
  241. return (int)self.deviceMap.count;
  242. }
  243. // manual API for folks to start & stop scanning
  244. - (void)startScan:(int)duration
  245. {
  246. NSLog( @"BLE: requesting scan for %d seconds", duration );
  247. @synchronized (self)
  248. {
  249. if ( _nPendingScans++ == 0 )
  250. {
  251. [self.centralManager scanForPeripheralsWithServices:nil options:nil];
  252. }
  253. }
  254. if ( duration != 0 )
  255. {
  256. dispatch_after( dispatch_time( DISPATCH_TIME_NOW, (int64_t)(duration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  257. [self stopScan];
  258. });
  259. }
  260. }
  261. - (void)stopScan
  262. {
  263. NSLog( @"BLE: stopping scan" );
  264. @synchronized (self)
  265. {
  266. if ( --_nPendingScans <= 0 )
  267. {
  268. _nPendingScans = 0;
  269. [self.centralManager stopScan];
  270. }
  271. }
  272. }
  273. #pragma mark CBCentralManagerDelegate Implementation
  274. // called whenever the BLE hardware state changes.
  275. - (void)centralManagerDidUpdateState:(CBCentralManager *)central
  276. {
  277. switch ( central.state )
  278. {
  279. case CBCentralManagerStatePoweredOn:
  280. {
  281. NSLog( @"CoreBluetooth BLE hardware is powered on and ready" );
  282. // at startup, if we have no already attached peripherals, do a 20s scan for new unpaired devices,
  283. // otherwise callers should occaisionally do additional scans. we don't want to continuously be
  284. // scanning because it drains battery, causes other nearby people to have a hard time pairing their
  285. // Steam Controllers, and may also trigger firmware weirdness when a device attempts to start
  286. // the pairing sequence multiple times concurrently
  287. if ( [self updateConnectedSteamControllers:false] == 0 )
  288. {
  289. // TODO: we could limit our scan to only peripherals supporting the SteamController service, but
  290. // that service doesn't currently fit in the base advertising packet, we'd need to put it into an
  291. // extended scan packet. Useful optimization downstream, but not currently necessary
  292. // NSArray *services = @[[CBUUID UUIDWithString:VALVE_SERVICE]];
  293. [self startScan:20];
  294. }
  295. break;
  296. }
  297. case CBCentralManagerStatePoweredOff:
  298. NSLog( @"CoreBluetooth BLE hardware is powered off" );
  299. break;
  300. case CBCentralManagerStateUnauthorized:
  301. NSLog( @"CoreBluetooth BLE state is unauthorized" );
  302. break;
  303. case CBCentralManagerStateUnknown:
  304. NSLog( @"CoreBluetooth BLE state is unknown" );
  305. break;
  306. case CBCentralManagerStateUnsupported:
  307. NSLog( @"CoreBluetooth BLE hardware is unsupported on this platform" );
  308. break;
  309. case CBCentralManagerStateResetting:
  310. NSLog( @"CoreBluetooth BLE manager is resetting" );
  311. break;
  312. }
  313. }
  314. - (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
  315. {
  316. HIDBLEDevice *steamController = [_deviceMap objectForKey:peripheral];
  317. steamController.connected = YES;
  318. self.nPendingPairs -= 1;
  319. }
  320. - (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
  321. {
  322. NSLog( @"Failed to connect: %@", error );
  323. [_deviceMap removeObjectForKey:peripheral];
  324. self.nPendingPairs -= 1;
  325. }
  326. - (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
  327. {
  328. NSString *localName = [advertisementData objectForKey:CBAdvertisementDataLocalNameKey];
  329. NSString *log = [NSString stringWithFormat:@"Found '%@'", localName];
  330. if ( [localName isEqualToString:@"SteamController"] )
  331. {
  332. NSLog( @"%@ : %@ - %@", log, peripheral, advertisementData );
  333. self.nPendingPairs += 1;
  334. HIDBLEDevice *steamController = [[HIDBLEDevice alloc] initWithPeripheral:peripheral];
  335. [self.deviceMap setObject:steamController forKey:peripheral];
  336. [self.centralManager connectPeripheral:peripheral options:nil];
  337. }
  338. }
  339. - (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
  340. {
  341. HIDBLEDevice *steamController = [self.deviceMap objectForKey:peripheral];
  342. if ( steamController )
  343. {
  344. steamController.connected = NO;
  345. steamController.ready = NO;
  346. [self.deviceMap removeObjectForKey:peripheral];
  347. }
  348. }
  349. @end
  350. // Core Bluetooth devices calling back on event boundaries of their run-loops. so annoying.
  351. static void process_pending_events()
  352. {
  353. CFRunLoopRunResult res;
  354. do
  355. {
  356. res = CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0.001, FALSE );
  357. }
  358. while( res != kCFRunLoopRunFinished && res != kCFRunLoopRunTimedOut );
  359. }
  360. @implementation HIDBLEDevice
  361. - (id)init
  362. {
  363. if ( self = [super init] )
  364. {
  365. RingBuffer_init( &_inputReports );
  366. self.bleSteamController = nil;
  367. self.bleCharacteristicInput = nil;
  368. self.bleCharacteristicReport = nil;
  369. _connected = NO;
  370. _ready = NO;
  371. }
  372. return self;
  373. }
  374. - (id)initWithPeripheral:(CBPeripheral *)peripheral
  375. {
  376. if ( self = [super init] )
  377. {
  378. RingBuffer_init( &_inputReports );
  379. _connected = NO;
  380. _ready = NO;
  381. self.bleSteamController = peripheral;
  382. if ( peripheral )
  383. {
  384. peripheral.delegate = self;
  385. }
  386. self.bleCharacteristicInput = nil;
  387. self.bleCharacteristicReport = nil;
  388. }
  389. return self;
  390. }
  391. - (void)setConnected:(bool)connected
  392. {
  393. _connected = connected;
  394. if ( _connected )
  395. {
  396. [_bleSteamController discoverServices:nil];
  397. }
  398. else
  399. {
  400. NSLog( @"Disconnected" );
  401. }
  402. }
  403. - (size_t)read_input_report:(uint8_t *)dst
  404. {
  405. if ( RingBuffer_read( &_inputReports, dst+1 ) )
  406. {
  407. *dst = 0x03;
  408. return 20;
  409. }
  410. return 0;
  411. }
  412. - (int)send_report:(const uint8_t *)data length:(size_t)length
  413. {
  414. [_bleSteamController writeValue:[NSData dataWithBytes:data length:length] forCharacteristic:_bleCharacteristicReport type:CBCharacteristicWriteWithResponse];
  415. return (int)length;
  416. }
  417. - (int)send_feature_report:(hidFeatureReport *)report
  418. {
  419. #if FEATURE_REPORT_LOGGING
  420. uint8_t *reportBytes = (uint8_t *)report;
  421. 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]", GetBluetoothSegmentSize( report->segment ),
  422. reportBytes[1], reportBytes[2], reportBytes[3], reportBytes[4], reportBytes[5], reportBytes[6],
  423. reportBytes[7], reportBytes[8], reportBytes[9], reportBytes[10], reportBytes[11], reportBytes[12],
  424. reportBytes[13], reportBytes[14], reportBytes[15], reportBytes[16], reportBytes[17], reportBytes[18],
  425. reportBytes[19] );
  426. #endif
  427. int sendSize = (int)GetBluetoothSegmentSize( &report->segment );
  428. if ( sendSize > 20 )
  429. sendSize = 20;
  430. #if 1
  431. // fire-and-forget - we are going to not wait for the response here because all Steam Controller BLE send_feature_report's are ignored,
  432. // except errors.
  433. [_bleSteamController writeValue:[NSData dataWithBytes:&report->segment length:sendSize] forCharacteristic:_bleCharacteristicReport type:CBCharacteristicWriteWithResponse];
  434. // pretend we received a result anybody cares about
  435. return 19;
  436. #else
  437. // this is technically the correct send_feature_report logic if you want to make sure it gets through and is
  438. // acknowledged or errors out
  439. _waitStateForWriteFeatureReport = BLEDeviceWaitState_Waiting;
  440. [_bleSteamController writeValue:[NSData dataWithBytes:&report->segment length:sendSize
  441. ] forCharacteristic:_bleCharacteristicReport type:CBCharacteristicWriteWithResponse];
  442. while ( _waitStateForWriteFeatureReport == BLEDeviceWaitState_Waiting )
  443. {
  444. process_pending_events();
  445. }
  446. if ( _waitStateForWriteFeatureReport == BLEDeviceWaitState_Error )
  447. {
  448. _waitStateForWriteFeatureReport = BLEDeviceWaitState_None;
  449. return -1;
  450. }
  451. _waitStateForWriteFeatureReport = BLEDeviceWaitState_None;
  452. return 19;
  453. #endif
  454. }
  455. - (int)get_feature_report:(uint8_t)feature into:(uint8_t *)buffer
  456. {
  457. _waitStateForReadFeatureReport = BLEDeviceWaitState_Waiting;
  458. [_bleSteamController readValueForCharacteristic:_bleCharacteristicReport];
  459. while ( _waitStateForReadFeatureReport == BLEDeviceWaitState_Waiting )
  460. process_pending_events();
  461. if ( _waitStateForReadFeatureReport == BLEDeviceWaitState_Error )
  462. {
  463. _waitStateForReadFeatureReport = BLEDeviceWaitState_None;
  464. return -1;
  465. }
  466. memcpy( buffer, _featureReport, sizeof(_featureReport) );
  467. _waitStateForReadFeatureReport = BLEDeviceWaitState_None;
  468. #if FEATURE_REPORT_LOGGING
  469. NSLog( @"HIDBLE:get_feature_report (19) [%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x]",
  470. buffer[1], buffer[2], buffer[3], buffer[4], buffer[5], buffer[6],
  471. buffer[7], buffer[8], buffer[9], buffer[10], buffer[11], buffer[12],
  472. buffer[13], buffer[14], buffer[15], buffer[16], buffer[17], buffer[18],
  473. buffer[19] );
  474. #endif
  475. return 19;
  476. }
  477. #pragma mark CBPeripheralDelegate Implementation
  478. - (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error
  479. {
  480. for (CBService *service in peripheral.services)
  481. {
  482. NSLog( @"Found Service: %@", service );
  483. if ( [service.UUID isEqual:[CBUUID UUIDWithString:VALVE_SERVICE]] )
  484. {
  485. [peripheral discoverCharacteristics:nil forService:service];
  486. }
  487. }
  488. }
  489. - (void)peripheral:(CBPeripheral *)peripheral didDiscoverDescriptorsForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
  490. {
  491. // nothing yet needed here, enable for logging
  492. if ( /* DISABLES CODE */ (0) )
  493. {
  494. for ( CBDescriptor *descriptor in characteristic.descriptors )
  495. {
  496. NSLog( @" - Descriptor '%@'", descriptor );
  497. }
  498. }
  499. }
  500. - (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
  501. {
  502. if ([service.UUID isEqual:[CBUUID UUIDWithString:VALVE_SERVICE]])
  503. {
  504. for (CBCharacteristic *aChar in service.characteristics)
  505. {
  506. NSLog( @"Found Characteristic %@", aChar );
  507. if ( [aChar.UUID isEqual:[CBUUID UUIDWithString:VALVE_INPUT_CHAR]] )
  508. {
  509. self.bleCharacteristicInput = aChar;
  510. }
  511. else if ( [aChar.UUID isEqual:[CBUUID UUIDWithString:VALVE_REPORT_CHAR]] )
  512. {
  513. self.bleCharacteristicReport = aChar;
  514. [self.bleSteamController discoverDescriptorsForCharacteristic: aChar];
  515. }
  516. }
  517. }
  518. }
  519. - (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
  520. {
  521. static uint64_t s_ticksLastOverflowReport = 0;
  522. // receiving an input report is the final indicator that the user accepted a pairing
  523. // request and that we successfully established notification. CoreBluetooth has no
  524. // notification of the pairing acknowledgement, which is a bad oversight.
  525. if ( self.ready == NO )
  526. {
  527. self.ready = YES;
  528. HIDBLEManager.sharedInstance.nPendingPairs -= 1;
  529. }
  530. if ( [characteristic.UUID isEqual:_bleCharacteristicInput.UUID] )
  531. {
  532. NSData *data = [characteristic value];
  533. if ( data.length != 19 )
  534. {
  535. NSLog( @"HIDBLE: incoming data is %lu bytes should be exactly 19", (unsigned long)data.length );
  536. }
  537. if ( !RingBuffer_write( &_inputReports, (const uint8_t *)data.bytes ) )
  538. {
  539. uint64_t ticksNow = mach_approximate_time();
  540. if ( ticksNow - s_ticksLastOverflowReport > (5ull * NSEC_PER_SEC / 10) )
  541. {
  542. NSLog( @"HIDBLE: input report buffer overflow" );
  543. s_ticksLastOverflowReport = ticksNow;
  544. }
  545. }
  546. }
  547. else if ( [characteristic.UUID isEqual:_bleCharacteristicReport.UUID] )
  548. {
  549. memset( _featureReport, 0, sizeof(_featureReport) );
  550. if ( error != nil )
  551. {
  552. NSLog( @"HIDBLE: get_feature_report error: %@", error );
  553. _waitStateForReadFeatureReport = BLEDeviceWaitState_Error;
  554. }
  555. else
  556. {
  557. NSData *data = [characteristic value];
  558. if ( data.length != 20 )
  559. {
  560. NSLog( @"HIDBLE: incoming data is %lu bytes should be exactly 20", (unsigned long)data.length );
  561. }
  562. memcpy( _featureReport, data.bytes, MIN( data.length, sizeof(_featureReport) ) );
  563. _waitStateForReadFeatureReport = BLEDeviceWaitState_Complete;
  564. }
  565. }
  566. }
  567. - (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
  568. {
  569. if ( [characteristic.UUID isEqual:[CBUUID UUIDWithString:VALVE_REPORT_CHAR]] )
  570. {
  571. if ( error != nil )
  572. {
  573. NSLog( @"HIDBLE: write_feature_report error: %@", error );
  574. _waitStateForWriteFeatureReport = BLEDeviceWaitState_Error;
  575. }
  576. else
  577. {
  578. _waitStateForWriteFeatureReport = BLEDeviceWaitState_Complete;
  579. }
  580. }
  581. }
  582. - (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
  583. {
  584. NSLog( @"didUpdateNotifcationStateForCharacteristic %@ (%@)", characteristic, error );
  585. }
  586. @end
  587. #pragma mark hid_api implementation
  588. struct hid_device_ {
  589. void *device_handle;
  590. int blocking;
  591. hid_device *next;
  592. };
  593. int HID_API_EXPORT HID_API_CALL hid_init(void)
  594. {
  595. return ( HIDBLEManager.sharedInstance == nil ) ? -1 : 0;
  596. }
  597. int HID_API_EXPORT HID_API_CALL hid_exit(void)
  598. {
  599. return 0;
  600. }
  601. void HID_API_EXPORT HID_API_CALL hid_ble_scan( bool bStart )
  602. {
  603. HIDBLEManager *bleManager = HIDBLEManager.sharedInstance;
  604. if ( bStart )
  605. {
  606. [bleManager startScan:0];
  607. }
  608. else
  609. {
  610. [bleManager stopScan];
  611. }
  612. }
  613. hid_device * HID_API_EXPORT hid_open_path( const char *path, int bExclusive /* = false */ )
  614. {
  615. hid_device *result = NULL;
  616. NSString *nssPath = [NSString stringWithUTF8String:path];
  617. HIDBLEManager *bleManager = HIDBLEManager.sharedInstance;
  618. NSEnumerator<HIDBLEDevice *> *devices = [bleManager.deviceMap objectEnumerator];
  619. for ( HIDBLEDevice *device in devices )
  620. {
  621. // we have the device but it hasn't found its service or characteristics until it is connected
  622. if ( !device.ready || !device.connected || !device.bleCharacteristicInput )
  623. continue;
  624. if ( [device.bleSteamController.identifier.UUIDString isEqualToString:nssPath] )
  625. {
  626. result = (hid_device *)malloc( sizeof( hid_device ) );
  627. memset( result, 0, sizeof( hid_device ) );
  628. result->device_handle = (void*)CFBridgingRetain( device );
  629. result->blocking = NO;
  630. // enable reporting input events on the characteristic
  631. [device.bleSteamController setNotifyValue:YES forCharacteristic:device.bleCharacteristicInput];
  632. return result;
  633. }
  634. }
  635. return result;
  636. }
  637. void HID_API_EXPORT hid_free_enumeration(struct hid_device_info *devs)
  638. {
  639. /* This function is identical to the Linux version. Platform independent. */
  640. struct hid_device_info *d = devs;
  641. while (d) {
  642. struct hid_device_info *next = d->next;
  643. free(d->path);
  644. free(d->serial_number);
  645. free(d->manufacturer_string);
  646. free(d->product_string);
  647. free(d);
  648. d = next;
  649. }
  650. }
  651. int HID_API_EXPORT hid_set_nonblocking(hid_device *dev, int nonblock)
  652. {
  653. /* All Nonblocking operation is handled by the library. */
  654. dev->blocking = !nonblock;
  655. return 0;
  656. }
  657. struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, unsigned short product_id)
  658. { @autoreleasepool {
  659. struct hid_device_info *root = NULL;
  660. if ( ( vendor_id == 0 && product_id == 0 ) ||
  661. ( vendor_id == VALVE_USB_VID && product_id == D0G_BLE2_PID ) )
  662. {
  663. HIDBLEManager *bleManager = HIDBLEManager.sharedInstance;
  664. [bleManager updateConnectedSteamControllers:false];
  665. NSEnumerator<HIDBLEDevice *> *devices = [bleManager.deviceMap objectEnumerator];
  666. for ( HIDBLEDevice *device in devices )
  667. {
  668. // there are several brief windows in connecting to an already paired device and
  669. // one long window waiting for users to confirm pairing where we don't want
  670. // to consider a device ready - if we hand it back to SDL or another
  671. // Steam Controller consumer, their additional SC setup work will fail
  672. // in unusual/silent ways and we can actually corrupt the BLE stack for
  673. // the entire system and kill the appletv remote's Menu button (!)
  674. if ( device.bleSteamController.state != CBPeripheralStateConnected ||
  675. device.connected == NO || device.ready == NO )
  676. {
  677. if ( device.ready == NO && device.bleCharacteristicInput != nil )
  678. {
  679. // attempt to register for input reports. this call will silently fail
  680. // until the pairing finalizes with user acceptance. oh, apple.
  681. [device.bleSteamController setNotifyValue:YES forCharacteristic:device.bleCharacteristicInput];
  682. }
  683. continue;
  684. }
  685. struct hid_device_info *device_info = (struct hid_device_info *)malloc( sizeof(struct hid_device_info) );
  686. memset( device_info, 0, sizeof(struct hid_device_info) );
  687. device_info->next = root;
  688. root = device_info;
  689. device_info->path = strdup( device.bleSteamController.identifier.UUIDString.UTF8String );
  690. device_info->vendor_id = VALVE_USB_VID;
  691. device_info->product_id = D0G_BLE2_PID;
  692. device_info->product_string = wcsdup( L"Steam Controller" );
  693. device_info->manufacturer_string = wcsdup( L"Valve Corporation" );
  694. }
  695. }
  696. return root;
  697. }}
  698. int HID_API_EXPORT_CALL hid_get_manufacturer_string(hid_device *dev, wchar_t *string, size_t maxlen)
  699. {
  700. static wchar_t s_wszManufacturer[] = L"Valve Corporation";
  701. wcsncpy( string, s_wszManufacturer, sizeof(s_wszManufacturer)/sizeof(s_wszManufacturer[0]) );
  702. return 0;
  703. }
  704. int HID_API_EXPORT_CALL hid_get_product_string(hid_device *dev, wchar_t *string, size_t maxlen)
  705. {
  706. static wchar_t s_wszProduct[] = L"Steam Controller";
  707. wcsncpy( string, s_wszProduct, sizeof(s_wszProduct)/sizeof(s_wszProduct[0]) );
  708. return 0;
  709. }
  710. int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen)
  711. {
  712. static wchar_t s_wszSerial[] = L"12345";
  713. wcsncpy( string, s_wszSerial, sizeof(s_wszSerial)/sizeof(s_wszSerial[0]) );
  714. return 0;
  715. }
  716. int HID_API_EXPORT hid_write(hid_device *dev, const unsigned char *data, size_t length)
  717. {
  718. HIDBLEDevice *device_handle = (__bridge HIDBLEDevice *)dev->device_handle;
  719. if ( !device_handle.connected )
  720. return -1;
  721. return [device_handle send_report:data length:length];
  722. }
  723. void HID_API_EXPORT hid_close(hid_device *dev)
  724. {
  725. HIDBLEDevice *device_handle = CFBridgingRelease( dev->device_handle );
  726. // disable reporting input events on the characteristic
  727. if ( device_handle.connected ) {
  728. [device_handle.bleSteamController setNotifyValue:NO forCharacteristic:device_handle.bleCharacteristicInput];
  729. }
  730. free( dev );
  731. }
  732. int HID_API_EXPORT hid_send_feature_report(hid_device *dev, const unsigned char *data, size_t length)
  733. {
  734. HIDBLEDevice *device_handle = (__bridge HIDBLEDevice *)dev->device_handle;
  735. if ( !device_handle.connected )
  736. return -1;
  737. return [device_handle send_feature_report:(hidFeatureReport *)(void *)data];
  738. }
  739. int HID_API_EXPORT hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length)
  740. {
  741. HIDBLEDevice *device_handle = (__bridge HIDBLEDevice *)dev->device_handle;
  742. if ( !device_handle.connected )
  743. return -1;
  744. size_t written = [device_handle get_feature_report:data[0] into:data];
  745. return written == length-1 ? (int)length : (int)written;
  746. }
  747. int HID_API_EXPORT hid_read(hid_device *dev, unsigned char *data, size_t length)
  748. {
  749. HIDBLEDevice *device_handle = (__bridge HIDBLEDevice *)dev->device_handle;
  750. if ( !device_handle.connected )
  751. return -1;
  752. return hid_read_timeout(dev, data, length, 0);
  753. }
  754. int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t length, int milliseconds)
  755. {
  756. HIDBLEDevice *device_handle = (__bridge HIDBLEDevice *)dev->device_handle;
  757. if ( !device_handle.connected )
  758. return -1;
  759. if ( milliseconds != 0 )
  760. {
  761. NSLog( @"hid_read_timeout with non-zero wait" );
  762. }
  763. int result = (int)[device_handle read_input_report:data];
  764. #if FEATURE_REPORT_LOGGING
  765. 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,
  766. data[1], data[2], data[3], data[4], data[5], data[6],
  767. data[7], data[8], data[9], data[10], data[11], data[12],
  768. data[13], data[14], data[15], data[16], data[17], data[18],
  769. data[19] );
  770. #endif
  771. return result;
  772. }
  773. #endif /* SDL_JOYSTICK_HIDAPI */