sdl

FORK: Simple Directmedia Layer
git clone https://git.neptards.moe/neptards/sdl.git
Log | Files | Refs

hid.m (29311B)


      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;
     47 	uint8_t		length;
     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;
     64 			uint8_t		length;
     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 			self.nPendingPairs += 1;
    286 			HIDBLEDevice *steamController = [[HIDBLEDevice alloc] initWithPeripheral:peripheral];
    287 			[self.deviceMap setObject:steamController forKey:peripheral];
    288 			[self.centralManager connectPeripheral:peripheral options:nil];
    289 		}
    290 	}
    291 
    292 	return (int)self.deviceMap.count;
    293 }
    294 
    295 // manual API for folks to start & stop scanning
    296 - (void)startScan:(int)duration
    297 {
    298 	NSLog( @"BLE: requesting scan for %d seconds", duration );
    299 	@synchronized (self)
    300 	{
    301 		if ( _nPendingScans++ == 0 )
    302 		{
    303 			[self.centralManager scanForPeripheralsWithServices:nil options:nil];
    304 		}
    305 	}
    306 
    307 	if ( duration != 0 )
    308 	{
    309 		dispatch_after( dispatch_time( DISPATCH_TIME_NOW, (int64_t)(duration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    310 			[self stopScan];
    311 		});
    312 	}
    313 }
    314 
    315 - (void)stopScan
    316 {
    317 	NSLog( @"BLE: stopping scan" );
    318 	@synchronized (self)
    319 	{
    320 		if ( --_nPendingScans <= 0 )
    321 		{
    322 			_nPendingScans = 0;
    323 			[self.centralManager stopScan];
    324 		}
    325 	}
    326 }
    327 
    328 
    329 #pragma mark CBCentralManagerDelegate Implementation
    330 
    331 // called whenever the BLE hardware state changes.
    332 - (void)centralManagerDidUpdateState:(CBCentralManager *)central
    333 {
    334 	switch ( central.state )
    335 	{
    336 		case CBCentralManagerStatePoweredOn:
    337 		{
    338 			NSLog( @"CoreBluetooth BLE hardware is powered on and ready" );
    339 			
    340 			// at startup, if we have no already attached peripherals, do a 20s scan for new unpaired devices,
    341 			// otherwise callers should occaisionally do additional scans. we don't want to continuously be
    342 			// scanning because it drains battery, causes other nearby people to have a hard time pairing their
    343 			// Steam Controllers, and may also trigger firmware weirdness when a device attempts to start
    344 			// the pairing sequence multiple times concurrently
    345 			if ( [self updateConnectedSteamControllers:false] == 0 )
    346 			{
    347 				// TODO: we could limit our scan to only peripherals supporting the SteamController service, but
    348 				//  that service doesn't currently fit in the base advertising packet, we'd need to put it into an
    349 				//  extended scan packet. Useful optimization downstream, but not currently necessary
    350 				//	NSArray *services = @[[CBUUID UUIDWithString:VALVE_SERVICE]];
    351 				[self startScan:20];
    352 			}
    353 			break;
    354 		}
    355 			
    356 		case CBCentralManagerStatePoweredOff:
    357 			NSLog( @"CoreBluetooth BLE hardware is powered off" );
    358 			break;
    359 			
    360 		case CBCentralManagerStateUnauthorized:
    361 			NSLog( @"CoreBluetooth BLE state is unauthorized" );
    362 			break;
    363 			
    364 		case CBCentralManagerStateUnknown:
    365 			NSLog( @"CoreBluetooth BLE state is unknown" );
    366 			break;
    367 			
    368 		case CBCentralManagerStateUnsupported:
    369 			NSLog( @"CoreBluetooth BLE hardware is unsupported on this platform" );
    370 			break;
    371 		
    372 		case CBCentralManagerStateResetting:
    373 			NSLog( @"CoreBluetooth BLE manager is resetting" );
    374 			break;
    375 	}
    376 }
    377 
    378 - (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
    379 {
    380 	HIDBLEDevice *steamController = [_deviceMap objectForKey:peripheral];
    381 	steamController.connected = YES;
    382 	self.nPendingPairs -= 1;
    383 }
    384 
    385 - (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
    386 {
    387 	NSLog( @"Failed to connect: %@", error );
    388 	[_deviceMap removeObjectForKey:peripheral];
    389 	self.nPendingPairs -= 1;
    390 }
    391 
    392 - (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
    393 {
    394 	NSString *localName = [advertisementData objectForKey:CBAdvertisementDataLocalNameKey];
    395 	NSString *log = [NSString stringWithFormat:@"Found '%@'", localName];
    396 	
    397 	if ( [localName isEqualToString:@"SteamController"] )
    398 	{
    399 		NSLog( @"%@ : %@ - %@", log, peripheral, advertisementData );
    400 		self.nPendingPairs += 1;
    401 		HIDBLEDevice *steamController = [[HIDBLEDevice alloc] initWithPeripheral:peripheral];
    402 		[self.deviceMap setObject:steamController forKey:peripheral];
    403 		[self.centralManager connectPeripheral:peripheral options:nil];
    404 	}
    405 }
    406 
    407 - (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
    408 {
    409 	HIDBLEDevice *steamController = [self.deviceMap objectForKey:peripheral];
    410 	if ( steamController )
    411 	{
    412 		steamController.connected = NO;
    413 		steamController.ready = NO;
    414 		[self.deviceMap removeObjectForKey:peripheral];
    415 	}
    416 }
    417 
    418 @end
    419 
    420 
    421 // Core Bluetooth devices calling back on event boundaries of their run-loops. so annoying.
    422 static void process_pending_events()
    423 {
    424 	CFRunLoopRunResult res;
    425 	do
    426 	{
    427 		res = CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0.001, FALSE );
    428 	}
    429 	while( res != kCFRunLoopRunFinished && res != kCFRunLoopRunTimedOut );
    430 }
    431 
    432 @implementation HIDBLEDevice
    433 
    434 - (id)init
    435 {
    436 	if ( self = [super init] )
    437 	{
    438         RingBuffer_init( &_inputReports );
    439 		self.bleSteamController = nil;
    440 		self.bleCharacteristicInput = nil;
    441 		self.bleCharacteristicReport = nil;
    442 		_connected = NO;
    443 		_ready = NO;
    444 	}
    445 	return self;
    446 }
    447 
    448 - (id)initWithPeripheral:(CBPeripheral *)peripheral
    449 {
    450 	if ( self = [super init] )
    451 	{
    452         RingBuffer_init( &_inputReports );
    453 		_connected = NO;
    454 		_ready = NO;
    455 		self.bleSteamController = peripheral;
    456 		if ( peripheral )
    457 		{
    458 			peripheral.delegate = self;
    459 		}
    460 		self.bleCharacteristicInput = nil;
    461 		self.bleCharacteristicReport = nil;
    462 	}
    463 	return self;
    464 }
    465 
    466 - (void)setConnected:(bool)connected
    467 {
    468 	_connected = connected;
    469 	if ( _connected )
    470 	{
    471 		[_bleSteamController discoverServices:nil];
    472 	}
    473 	else
    474 	{
    475 		NSLog( @"Disconnected" );
    476 	}
    477 }
    478 
    479 - (size_t)read_input_report:(uint8_t *)dst
    480 {
    481 	if ( RingBuffer_read( &_inputReports, dst+1 ) )
    482 	{
    483 		*dst = 0x03;
    484 		return 20;
    485 	}
    486 	return 0;
    487 }
    488 
    489 - (int)send_report:(const uint8_t *)data length:(size_t)length
    490 {
    491 	[_bleSteamController writeValue:[NSData dataWithBytes:data length:length] forCharacteristic:_bleCharacteristicReport type:CBCharacteristicWriteWithResponse];
    492 	return (int)length;
    493 }
    494 
    495 - (int)send_feature_report:(hidFeatureReport *)report
    496 {
    497 #if FEATURE_REPORT_LOGGING
    498 	uint8_t *reportBytes = (uint8_t *)report;
    499 	
    500 	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 ),
    501 		  reportBytes[1], reportBytes[2], reportBytes[3], reportBytes[4], reportBytes[5], reportBytes[6],
    502 		  reportBytes[7], reportBytes[8], reportBytes[9], reportBytes[10], reportBytes[11], reportBytes[12],
    503 		  reportBytes[13], reportBytes[14], reportBytes[15], reportBytes[16], reportBytes[17], reportBytes[18],
    504 		  reportBytes[19] );
    505 #endif
    506 
    507 	int sendSize = (int)GetBluetoothSegmentSize( &report->segment );
    508 	if ( sendSize > 20 )
    509 		sendSize = 20;
    510 
    511 #if 1
    512 	// fire-and-forget - we are going to not wait for the response here because all Steam Controller BLE send_feature_report's are ignored,
    513 	//  except errors.
    514 	[_bleSteamController writeValue:[NSData dataWithBytes:&report->segment length:sendSize] forCharacteristic:_bleCharacteristicReport type:CBCharacteristicWriteWithResponse];
    515 	
    516 	// pretend we received a result anybody cares about
    517 	return 19;
    518 
    519 #else
    520 	// this is technically the correct send_feature_report logic if you want to make sure it gets through and is
    521 	// acknowledged or errors out
    522 	_waitStateForWriteFeatureReport = BLEDeviceWaitState_Waiting;
    523 	[_bleSteamController writeValue:[NSData dataWithBytes:&report->segment length:sendSize
    524 									 ] forCharacteristic:_bleCharacteristicReport type:CBCharacteristicWriteWithResponse];
    525 	
    526 	while ( _waitStateForWriteFeatureReport == BLEDeviceWaitState_Waiting )
    527 	{
    528 		process_pending_events();
    529 	}
    530 	
    531 	if ( _waitStateForWriteFeatureReport == BLEDeviceWaitState_Error )
    532 	{
    533 		_waitStateForWriteFeatureReport = BLEDeviceWaitState_None;
    534 		return -1;
    535 	}
    536 	
    537 	_waitStateForWriteFeatureReport = BLEDeviceWaitState_None;
    538 	return 19;
    539 #endif
    540 }
    541 
    542 - (int)get_feature_report:(uint8_t)feature into:(uint8_t *)buffer
    543 {
    544 	_waitStateForReadFeatureReport = BLEDeviceWaitState_Waiting;
    545 	[_bleSteamController readValueForCharacteristic:_bleCharacteristicReport];
    546 	
    547 	while ( _waitStateForReadFeatureReport == BLEDeviceWaitState_Waiting )
    548 		process_pending_events();
    549 	
    550 	if ( _waitStateForReadFeatureReport == BLEDeviceWaitState_Error )
    551 	{
    552 		_waitStateForReadFeatureReport = BLEDeviceWaitState_None;
    553 		return -1;
    554 	}
    555 	
    556 	memcpy( buffer, _featureReport, sizeof(_featureReport) );
    557 	
    558 	_waitStateForReadFeatureReport = BLEDeviceWaitState_None;
    559 	
    560 #if FEATURE_REPORT_LOGGING
    561 	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]",
    562 		  buffer[1], buffer[2], buffer[3], buffer[4], buffer[5], buffer[6],
    563 		  buffer[7], buffer[8], buffer[9], buffer[10], buffer[11], buffer[12],
    564 		  buffer[13], buffer[14], buffer[15], buffer[16], buffer[17], buffer[18],
    565 		  buffer[19] );
    566 #endif
    567 
    568 	return 19;
    569 }
    570 
    571 #pragma mark CBPeripheralDelegate Implementation
    572 
    573 - (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error
    574 {
    575 	for (CBService *service in peripheral.services)
    576 	{
    577 		NSLog( @"Found Service: %@", service );
    578 		if ( [service.UUID isEqual:[CBUUID UUIDWithString:VALVE_SERVICE]] )
    579 		{
    580 			[peripheral discoverCharacteristics:nil forService:service];
    581 		}
    582 	}
    583 }
    584 
    585 - (void)peripheral:(CBPeripheral *)peripheral didDiscoverDescriptorsForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
    586 {
    587 	// nothing yet needed here, enable for logging
    588 	if ( /* DISABLES CODE */ (0) )
    589 	{
    590 		for ( CBDescriptor *descriptor in characteristic.descriptors )
    591 		{
    592 			NSLog( @" - Descriptor '%@'", descriptor );
    593 		}
    594 	}
    595 }
    596 
    597 - (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
    598 {
    599 	if ([service.UUID isEqual:[CBUUID UUIDWithString:VALVE_SERVICE]])
    600 	{
    601 		for (CBCharacteristic *aChar in service.characteristics)
    602 		{
    603 			NSLog( @"Found Characteristic %@", aChar );
    604 			
    605 			if ( [aChar.UUID isEqual:[CBUUID UUIDWithString:VALVE_INPUT_CHAR]] )
    606 			{
    607 				self.bleCharacteristicInput = aChar;
    608 			}
    609 			else if ( [aChar.UUID isEqual:[CBUUID UUIDWithString:VALVE_REPORT_CHAR]] )
    610 			{
    611 				self.bleCharacteristicReport = aChar;
    612 				[self.bleSteamController discoverDescriptorsForCharacteristic: aChar];
    613 			}
    614 		}
    615 	}
    616 }
    617 
    618 - (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
    619 {
    620 	static uint64_t s_ticksLastOverflowReport = 0;
    621 
    622 	// receiving an input report is the final indicator that the user accepted a pairing
    623 	// request and that we successfully established notification. CoreBluetooth has no
    624 	// notification of the pairing acknowledgement, which is a bad oversight.
    625 	if ( self.ready == NO )
    626 	{
    627 		self.ready = YES;
    628 		HIDBLEManager.sharedInstance.nPendingPairs -= 1;
    629 	}
    630 
    631 	if ( [characteristic.UUID isEqual:_bleCharacteristicInput.UUID] )
    632 	{
    633 		NSData *data = [characteristic value];
    634 		if ( data.length != 19 )
    635 		{
    636 			NSLog( @"HIDBLE: incoming data is %lu bytes should be exactly 19", (unsigned long)data.length );
    637 		}
    638 		if ( !RingBuffer_write( &_inputReports, (const uint8_t *)data.bytes ) )
    639 		{
    640 			uint64_t ticksNow = mach_approximate_time();
    641 			if ( ticksNow - s_ticksLastOverflowReport > (5ull * NSEC_PER_SEC / 10) )
    642 			{
    643 				NSLog( @"HIDBLE: input report buffer overflow" );
    644 				s_ticksLastOverflowReport = ticksNow;
    645 			}
    646 		}
    647 	}
    648 	else if ( [characteristic.UUID isEqual:_bleCharacteristicReport.UUID] )
    649 	{
    650 		memset( _featureReport, 0, sizeof(_featureReport) );
    651 		
    652 		if ( error != nil )
    653 		{
    654 			NSLog( @"HIDBLE: get_feature_report error: %@", error );
    655 			_waitStateForReadFeatureReport = BLEDeviceWaitState_Error;
    656 		}
    657 		else
    658 		{
    659 			NSData *data = [characteristic value];
    660 			if ( data.length != 20 )
    661 			{
    662 				NSLog( @"HIDBLE: incoming data is %lu bytes should be exactly 20", (unsigned long)data.length );
    663 			}
    664 			memcpy( _featureReport, data.bytes, MIN( data.length, sizeof(_featureReport) ) );
    665 			_waitStateForReadFeatureReport = BLEDeviceWaitState_Complete;
    666 		}
    667 	}
    668 }
    669 
    670 - (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
    671 {
    672 	if ( [characteristic.UUID isEqual:[CBUUID UUIDWithString:VALVE_REPORT_CHAR]] )
    673 	{
    674 		if ( error != nil )
    675 		{
    676 			NSLog( @"HIDBLE: write_feature_report error: %@", error );
    677 			_waitStateForWriteFeatureReport = BLEDeviceWaitState_Error;
    678 		}
    679 		else
    680 		{
    681 			_waitStateForWriteFeatureReport = BLEDeviceWaitState_Complete;
    682 		}
    683 	}
    684 }
    685 
    686 - (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
    687 {
    688 	NSLog( @"didUpdateNotifcationStateForCharacteristic %@ (%@)", characteristic, error );
    689 }
    690 
    691 @end
    692 
    693 
    694 #pragma mark hid_api implementation
    695 
    696 struct hid_device_ {
    697 	void *device_handle;
    698 	int blocking;
    699 	hid_device *next;
    700 };
    701 
    702 int HID_API_EXPORT HID_API_CALL hid_init(void)
    703 {
    704 	return ( HIDBLEManager.sharedInstance == nil ) ? -1 : 0;
    705 }
    706 
    707 int HID_API_EXPORT HID_API_CALL hid_exit(void)
    708 {
    709 	return 0;
    710 }
    711 
    712 void HID_API_EXPORT HID_API_CALL hid_ble_scan( bool bStart )
    713 {
    714 	HIDBLEManager *bleManager = HIDBLEManager.sharedInstance;
    715 	if ( bStart )
    716 	{
    717 		[bleManager startScan:0];
    718 	}
    719 	else
    720 	{
    721 		[bleManager stopScan];
    722 	}
    723 }
    724 
    725 hid_device * HID_API_EXPORT hid_open_path( const char *path, int bExclusive /* = false */ )
    726 {
    727 	hid_device *result = NULL;
    728 	NSString *nssPath = [NSString stringWithUTF8String:path];
    729 	HIDBLEManager *bleManager = HIDBLEManager.sharedInstance;
    730 	NSEnumerator<HIDBLEDevice *> *devices = [bleManager.deviceMap objectEnumerator];
    731 	
    732 	for ( HIDBLEDevice *device in devices )
    733 	{
    734 		// we have the device but it hasn't found its service or characteristics until it is connected
    735 		if ( !device.ready || !device.connected || !device.bleCharacteristicInput )
    736 			continue;
    737 		
    738 		if ( [device.bleSteamController.identifier.UUIDString isEqualToString:nssPath] )
    739 		{
    740 			result = (hid_device *)malloc( sizeof( hid_device ) );
    741 			memset( result, 0, sizeof( hid_device ) );
    742 			result->device_handle = (void*)CFBridgingRetain( device );
    743 			result->blocking = NO;
    744 			// enable reporting input events on the characteristic
    745 			[device.bleSteamController setNotifyValue:YES forCharacteristic:device.bleCharacteristicInput];
    746 			return result;
    747 		}
    748 	}
    749 	return result;
    750 }
    751 
    752 void  HID_API_EXPORT hid_free_enumeration(struct hid_device_info *devs)
    753 {
    754 	/* This function is identical to the Linux version. Platform independent. */
    755 	struct hid_device_info *d = devs;
    756 	while (d) {
    757 		struct hid_device_info *next = d->next;
    758 		free(d->path);
    759 		free(d->serial_number);
    760 		free(d->manufacturer_string);
    761 		free(d->product_string);
    762 		free(d);
    763 		d = next;
    764 	}
    765 }
    766 
    767 int HID_API_EXPORT hid_set_nonblocking(hid_device *dev, int nonblock)
    768 {
    769 	/* All Nonblocking operation is handled by the library. */
    770 	dev->blocking = !nonblock;
    771 	
    772 	return 0;
    773 }
    774 
    775 struct hid_device_info  HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, unsigned short product_id)
    776 { @autoreleasepool {
    777 	struct hid_device_info *root = NULL;
    778 	
    779 	if ( ( vendor_id == 0 && product_id == 0 ) ||
    780 		 ( vendor_id == VALVE_USB_VID && product_id == D0G_BLE2_PID ) )
    781 	{
    782 		HIDBLEManager *bleManager = HIDBLEManager.sharedInstance;
    783 		[bleManager updateConnectedSteamControllers:false];
    784 		NSEnumerator<HIDBLEDevice *> *devices = [bleManager.deviceMap objectEnumerator];
    785 		for ( HIDBLEDevice *device in devices )
    786 		{
    787 			// there are several brief windows in connecting to an already paired device and
    788 			// one long window waiting for users to confirm pairing where we don't want
    789 			// to consider a device ready - if we hand it back to SDL or another
    790 			// Steam Controller consumer, their additional SC setup work will fail
    791 			// in unusual/silent ways and we can actually corrupt the BLE stack for
    792 			// the entire system and kill the appletv remote's Menu button (!)
    793 			if ( device.bleSteamController.state != CBPeripheralStateConnected ||
    794 				 device.connected == NO || device.ready == NO )
    795 			{
    796 				if ( device.ready == NO && device.bleCharacteristicInput != nil )
    797 				{
    798 					// attempt to register for input reports. this call will silently fail
    799 					// until the pairing finalizes with user acceptance. oh, apple.
    800 					[device.bleSteamController setNotifyValue:YES forCharacteristic:device.bleCharacteristicInput];
    801 				}
    802 				continue;
    803 			}
    804 			struct hid_device_info *device_info = (struct hid_device_info *)malloc( sizeof(struct hid_device_info) );
    805 			memset( device_info, 0, sizeof(struct hid_device_info) );
    806 			device_info->next = root;
    807 			root = device_info;
    808 			device_info->path = strdup( device.bleSteamController.identifier.UUIDString.UTF8String );
    809 			device_info->vendor_id = VALVE_USB_VID;
    810 			device_info->product_id = D0G_BLE2_PID;
    811 			device_info->product_string = wcsdup( L"Steam Controller" );
    812 			device_info->manufacturer_string = wcsdup( L"Valve Corporation" );
    813 		}
    814 	}
    815 	return root;
    816 }}
    817 
    818 int HID_API_EXPORT_CALL hid_get_manufacturer_string(hid_device *dev, wchar_t *string, size_t maxlen)
    819 {
    820 	static wchar_t s_wszManufacturer[] = L"Valve Corporation";
    821 	wcsncpy( string, s_wszManufacturer, sizeof(s_wszManufacturer)/sizeof(s_wszManufacturer[0]) );
    822 	return 0;
    823 }
    824 
    825 int HID_API_EXPORT_CALL hid_get_product_string(hid_device *dev, wchar_t *string, size_t maxlen)
    826 {
    827 	static wchar_t s_wszProduct[] = L"Steam Controller";
    828 	wcsncpy( string, s_wszProduct, sizeof(s_wszProduct)/sizeof(s_wszProduct[0]) );
    829 	return 0;
    830 }
    831 
    832 int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen)
    833 {
    834 	static wchar_t s_wszSerial[] = L"12345";
    835 	wcsncpy( string, s_wszSerial, sizeof(s_wszSerial)/sizeof(s_wszSerial[0]) );
    836 	return 0;
    837 }
    838 
    839 int HID_API_EXPORT hid_write(hid_device *dev, const unsigned char *data, size_t length)
    840 {
    841     HIDBLEDevice *device_handle = (__bridge HIDBLEDevice *)dev->device_handle;
    842 
    843 	if ( !device_handle.connected )
    844 		return -1;
    845 
    846 	return [device_handle send_report:data length:length];
    847 }
    848 
    849 void HID_API_EXPORT hid_close(hid_device *dev)
    850 {
    851     HIDBLEDevice *device_handle = CFBridgingRelease( dev->device_handle );
    852 
    853 	// disable reporting input events on the characteristic
    854 	if ( device_handle.connected ) {
    855 		[device_handle.bleSteamController setNotifyValue:NO forCharacteristic:device_handle.bleCharacteristicInput];
    856 	}
    857 
    858 	free( dev );
    859 }
    860 
    861 int HID_API_EXPORT hid_send_feature_report(hid_device *dev, const unsigned char *data, size_t length)
    862 {
    863     HIDBLEDevice *device_handle = (__bridge HIDBLEDevice *)dev->device_handle;
    864 
    865 	if ( !device_handle.connected )
    866 		return -1;
    867 
    868 	return [device_handle send_feature_report:(hidFeatureReport *)(void *)data];
    869 }
    870 
    871 int HID_API_EXPORT hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length)
    872 {
    873     HIDBLEDevice *device_handle = (__bridge HIDBLEDevice *)dev->device_handle;
    874 
    875 	if ( !device_handle.connected )
    876 		return -1;
    877 
    878 	size_t written = [device_handle get_feature_report:data[0] into:data];
    879 	
    880 	return written == length-1 ? (int)length : (int)written;
    881 }
    882 
    883 int HID_API_EXPORT hid_read(hid_device *dev, unsigned char *data, size_t length)
    884 {
    885     HIDBLEDevice *device_handle = (__bridge HIDBLEDevice *)dev->device_handle;
    886 
    887 	if ( !device_handle.connected )
    888 		return -1;
    889 
    890 	return hid_read_timeout(dev, data, length, 0);
    891 }
    892 
    893 int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t length, int milliseconds)
    894 {
    895     HIDBLEDevice *device_handle = (__bridge HIDBLEDevice *)dev->device_handle;
    896 
    897 	if ( !device_handle.connected )
    898 		return -1;
    899 	
    900 	if ( milliseconds != 0 )
    901 	{
    902 		NSLog( @"hid_read_timeout with non-zero wait" );
    903 	}
    904 	int result = (int)[device_handle read_input_report:data];
    905 #if FEATURE_REPORT_LOGGING
    906 	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,
    907 		  data[1], data[2], data[3], data[4], data[5], data[6],
    908 		  data[7], data[8], data[9], data[10], data[11], data[12],
    909 		  data[13], data[14], data[15], data[16], data[17], data[18],
    910 		  data[19] );
    911 #endif
    912 	return result;
    913 }
    914 
    915 #endif /* SDL_JOYSTICK_HIDAPI */