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