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