Removed dependency on C++ runtime on iOS
authorSam Lantinga <slouken@libsdl.org>
Tue, 21 Aug 2018 19:42:19 -0700
changeset 12136ec2cee693e4b
parent 12135 20df200b97ae
child 12137 161f67f4a7c6
Removed dependency on C++ runtime on iOS
Xcode-iOS/SDL/SDL.xcodeproj/project.pbxproj
src/hidapi/ios/hid.m
src/hidapi/ios/hid.mm
     1.1 --- a/Xcode-iOS/SDL/SDL.xcodeproj/project.pbxproj	Tue Aug 21 17:24:12 2018 -0700
     1.2 +++ b/Xcode-iOS/SDL/SDL.xcodeproj/project.pbxproj	Tue Aug 21 19:42:19 2018 -0700
     1.3 @@ -203,7 +203,8 @@
     1.4  		F30D9CA5212CD0BF0047DF2E /* SDL_coremotionsensor.m in Sources */ = {isa = PBXBuildFile; fileRef = F30D9CA3212CD0BF0047DF2E /* SDL_coremotionsensor.m */; };
     1.5  		F30D9CA6212CD0BF0047DF2E /* SDL_coremotionsensor.m in Sources */ = {isa = PBXBuildFile; fileRef = F30D9CA3212CD0BF0047DF2E /* SDL_coremotionsensor.m */; };
     1.6  		F30D9CA7212CD0BF0047DF2E /* SDL_coremotionsensor.h in Headers */ = {isa = PBXBuildFile; fileRef = F30D9CA4212CD0BF0047DF2E /* SDL_coremotionsensor.h */; };
     1.7 -		F3BDD77620F51C3C004ECBF3 /* hid.mm in Sources */ = {isa = PBXBuildFile; fileRef = F3BDD77520F51C3C004ECBF3 /* hid.mm */; };
     1.8 +		F30D9CC6212CE92C0047DF2E /* hid.m in Sources */ = {isa = PBXBuildFile; fileRef = F30D9CC5212CE92C0047DF2E /* hid.m */; };
     1.9 +		F30D9CC7212CE92C0047DF2E /* hid.m in Sources */ = {isa = PBXBuildFile; fileRef = F30D9CC5212CE92C0047DF2E /* hid.m */; };
    1.10  		F3BDD79220F51CB8004ECBF3 /* SDL_hidapi_xbox360.c in Sources */ = {isa = PBXBuildFile; fileRef = F3BDD78B20F51CB8004ECBF3 /* SDL_hidapi_xbox360.c */; };
    1.11  		F3BDD79320F51CB8004ECBF3 /* SDL_hidapi_xbox360.c in Sources */ = {isa = PBXBuildFile; fileRef = F3BDD78B20F51CB8004ECBF3 /* SDL_hidapi_xbox360.c */; };
    1.12  		F3BDD79420F51CB8004ECBF3 /* SDL_hidapi_switch.c in Sources */ = {isa = PBXBuildFile; fileRef = F3BDD78C20F51CB8004ECBF3 /* SDL_hidapi_switch.c */; };
    1.13 @@ -535,7 +536,7 @@
    1.14  		F30D9C9D212CD0990047DF2E /* SDL_sensor.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_sensor.c; sourceTree = "<group>"; };
    1.15  		F30D9CA3212CD0BF0047DF2E /* SDL_coremotionsensor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDL_coremotionsensor.m; sourceTree = "<group>"; };
    1.16  		F30D9CA4212CD0BF0047DF2E /* SDL_coremotionsensor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_coremotionsensor.h; sourceTree = "<group>"; };
    1.17 -		F3BDD77520F51C3C004ECBF3 /* hid.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = hid.mm; sourceTree = "<group>"; };
    1.18 +		F30D9CC5212CE92C0047DF2E /* hid.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = hid.m; sourceTree = "<group>"; };
    1.19  		F3BDD78B20F51CB8004ECBF3 /* SDL_hidapi_xbox360.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_xbox360.c; sourceTree = "<group>"; };
    1.20  		F3BDD78C20F51CB8004ECBF3 /* SDL_hidapi_switch.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_switch.c; sourceTree = "<group>"; };
    1.21  		F3BDD78D20F51CB8004ECBF3 /* SDL_hidapi_xboxone.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_xboxone.c; sourceTree = "<group>"; };
    1.22 @@ -859,7 +860,7 @@
    1.23  		F3BDD77420F51C18004ECBF3 /* ios */ = {
    1.24  			isa = PBXGroup;
    1.25  			children = (
    1.26 -				F3BDD77520F51C3C004ECBF3 /* hid.mm */,
    1.27 +				F30D9CC5212CE92C0047DF2E /* hid.m */,
    1.28  			);
    1.29  			path = ios;
    1.30  			sourceTree = "<group>";
    1.31 @@ -1520,6 +1521,7 @@
    1.32  				FAB598421BB5C31500BE72C5 /* SDL_quit.c in Sources */,
    1.33  				FAB598441BB5C31500BE72C5 /* SDL_touch.c in Sources */,
    1.34  				FAB598461BB5C31500BE72C5 /* SDL_windowevents.c in Sources */,
    1.35 +				F30D9CC7212CE92C0047DF2E /* hid.m in Sources */,
    1.36  				FAB598491BB5C31600BE72C5 /* SDL_rwopsbundlesupport.m in Sources */,
    1.37  				FAB5984A1BB5C31600BE72C5 /* SDL_rwops.c in Sources */,
    1.38  				FAB5984B1BB5C31600BE72C5 /* SDL_sysfilesystem.m in Sources */,
    1.39 @@ -1631,6 +1633,7 @@
    1.40  				FD6526750DE8FCDD002AD96B /* SDL_windowevents.c in Sources */,
    1.41  				4D7516FB1EE1C28A00820EEA /* SDL_uikitmetalview.m in Sources */,
    1.42  				FD6526760DE8FCDD002AD96B /* SDL_rwops.c in Sources */,
    1.43 +				F30D9CC6212CE92C0047DF2E /* hid.m in Sources */,
    1.44  				4D7517201EE1D98200820EEA /* SDL_vulkan_utils.c in Sources */,
    1.45  				FD6526780DE8FCDD002AD96B /* SDL_error.c in Sources */,
    1.46  				FD65267A0DE8FCDD002AD96B /* SDL.c in Sources */,
    1.47 @@ -1716,7 +1719,6 @@
    1.48  				AA628ADB159369E3005138DD /* SDL_rotate.c in Sources */,
    1.49  				AA126AD51617C5E7005ABC8F /* SDL_uikitmodes.m in Sources */,
    1.50  				AA704DD7162AA90A0076D1C1 /* SDL_dropevents.c in Sources */,
    1.51 -				F3BDD77620F51C3C004ECBF3 /* hid.mm in Sources */,
    1.52  				AABCC3951640643D00AB8930 /* SDL_uikitmessagebox.m in Sources */,
    1.53  				AA0AD06216647BBB00CE5896 /* SDL_gamecontroller.c in Sources */,
    1.54  				AA0F8495178D5F1A00823F9D /* SDL_systls.c in Sources */,
     2.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     2.2 +++ b/src/hidapi/ios/hid.m	Tue Aug 21 19:42:19 2018 -0700
     2.3 @@ -0,0 +1,909 @@
     2.4 +//======== Copyright (c) 2017 Valve Corporation, All rights reserved. =========
     2.5 +//
     2.6 +// Purpose: HID device abstraction temporary stub
     2.7 +//
     2.8 +//=============================================================================
     2.9 +
    2.10 +#include <CoreBluetooth/CoreBluetooth.h>
    2.11 +#include <QuartzCore/QuartzCore.h>
    2.12 +#import <UIKit/UIKit.h>
    2.13 +#import <mach/mach_time.h>
    2.14 +#include <pthread.h>
    2.15 +#include <sys/time.h>
    2.16 +#include <unistd.h>
    2.17 +#include "../hidapi/hidapi.h"
    2.18 +
    2.19 +#define VALVE_USB_VID       0x28DE
    2.20 +#define D0G_BLE2_PID        0x1106
    2.21 +
    2.22 +typedef uint32_t uint32;
    2.23 +typedef uint64_t uint64;
    2.24 +
    2.25 +// enables detailed NSLog logging of feature reports
    2.26 +#define FEATURE_REPORT_LOGGING	0
    2.27 +
    2.28 +#define REPORT_SEGMENT_DATA_FLAG	0x80
    2.29 +#define REPORT_SEGMENT_LAST_FLAG	0x40
    2.30 +
    2.31 +#define VALVE_SERVICE		@"100F6C32-1735-4313-B402-38567131E5F3"
    2.32 +
    2.33 +// (READ/NOTIFICATIONS)
    2.34 +#define VALVE_INPUT_CHAR	@"100F6C33-1735-4313-B402-38567131E5F3"
    2.35 +
    2.36 +//  (READ/WRITE)
    2.37 +#define VALVE_REPORT_CHAR	@"100F6C34-1735-4313-B402-38567131E5F3"
    2.38 +
    2.39 +// TODO: create CBUUID's in __attribute__((constructor)) rather than doing [CBUUID UUIDWithString:...] everywhere
    2.40 +
    2.41 +#pragma pack(push,1)
    2.42 +
    2.43 +typedef struct
    2.44 +{
    2.45 +	uint8_t		segmentHeader;
    2.46 +	uint8_t		featureReportMessageID;
    2.47 +	uint8_t		length;
    2.48 +	uint8_t		settingIdentifier;
    2.49 +	union {
    2.50 +		uint16_t	usPayload;
    2.51 +		uint32_t	uPayload;
    2.52 +		uint64_t	ulPayload;
    2.53 +		uint8_t		ucPayload[15];
    2.54 +	};
    2.55 +} bluetoothSegment;
    2.56 +
    2.57 +typedef struct {
    2.58 +	uint8_t		id;
    2.59 +	union {
    2.60 +		bluetoothSegment segment;
    2.61 +		struct {
    2.62 +			uint8_t		segmentHeader;
    2.63 +			uint8_t		featureReportMessageID;
    2.64 +			uint8_t		length;
    2.65 +			uint8_t		settingIdentifier;
    2.66 +			union {
    2.67 +				uint16_t	usPayload;
    2.68 +				uint32_t	uPayload;
    2.69 +				uint64_t	ulPayload;
    2.70 +				uint8_t		ucPayload[15];
    2.71 +			};
    2.72 +		};
    2.73 +	};
    2.74 +} hidFeatureReport;
    2.75 +
    2.76 +#pragma pack(pop)
    2.77 +
    2.78 +size_t GetBluetoothSegmentSize(bluetoothSegment *segment)
    2.79 +{
    2.80 +    return segment->length + 3;
    2.81 +}
    2.82 +
    2.83 +#define RingBuffer_cbElem   19
    2.84 +#define RingBuffer_nElem    4096
    2.85 +
    2.86 +typedef struct {
    2.87 +	int _first, _last;
    2.88 +	uint8_t _data[ ( RingBuffer_nElem * RingBuffer_cbElem ) ];
    2.89 +	pthread_mutex_t accessLock;
    2.90 +} RingBuffer;
    2.91 +
    2.92 +static void RingBuffer_init( RingBuffer *this )
    2.93 +{
    2.94 +    this->_first = -1;
    2.95 +    this->_last = 0;
    2.96 +    pthread_mutex_init( &this->accessLock, 0 );
    2.97 +}
    2.98 +	
    2.99 +static bool RingBuffer_write( RingBuffer *this, const uint8_t *src )
   2.100 +{
   2.101 +    pthread_mutex_lock( &this->accessLock );
   2.102 +    memcpy( &this->_data[ this->_last ], src, RingBuffer_cbElem );
   2.103 +    if ( this->_first == -1 )
   2.104 +    {
   2.105 +        this->_first = this->_last;
   2.106 +    }
   2.107 +    this->_last = ( this->_last + RingBuffer_cbElem ) % (RingBuffer_nElem * RingBuffer_cbElem);
   2.108 +    if ( this->_last == this->_first )
   2.109 +    {
   2.110 +        this->_first = ( this->_first + RingBuffer_cbElem ) % (RingBuffer_nElem * RingBuffer_cbElem);
   2.111 +        pthread_mutex_unlock( &this->accessLock );
   2.112 +        return false;
   2.113 +    }
   2.114 +    pthread_mutex_unlock( &this->accessLock );
   2.115 +    return true;
   2.116 +}
   2.117 +
   2.118 +static bool RingBuffer_read( RingBuffer *this, uint8_t *dst )
   2.119 +{
   2.120 +    pthread_mutex_lock( &this->accessLock );
   2.121 +    if ( this->_first == -1 )
   2.122 +    {
   2.123 +        pthread_mutex_unlock( &this->accessLock );
   2.124 +        return false;
   2.125 +    }
   2.126 +    memcpy( dst, &this->_data[ this->_first ], RingBuffer_cbElem );
   2.127 +    this->_first = ( this->_first + RingBuffer_cbElem ) % (RingBuffer_nElem * RingBuffer_cbElem);
   2.128 +    if ( this->_first == this->_last )
   2.129 +    {
   2.130 +        this->_first = -1;
   2.131 +    }
   2.132 +    pthread_mutex_unlock( &this->accessLock );
   2.133 +    return true;
   2.134 +}
   2.135 +
   2.136 +
   2.137 +#pragma mark HIDBLEDevice Definition
   2.138 +
   2.139 +typedef enum
   2.140 +{
   2.141 +	BLEDeviceWaitState_None,
   2.142 +	BLEDeviceWaitState_Waiting,
   2.143 +	BLEDeviceWaitState_Complete,
   2.144 +	BLEDeviceWaitState_Error
   2.145 +} BLEDeviceWaitState;
   2.146 +
   2.147 +@interface HIDBLEDevice : NSObject <CBPeripheralDelegate>
   2.148 +{
   2.149 +	RingBuffer _inputReports;
   2.150 +	uint8_t	_featureReport[20];
   2.151 +	BLEDeviceWaitState	_waitStateForReadFeatureReport;
   2.152 +	BLEDeviceWaitState	_waitStateForWriteFeatureReport;
   2.153 +}
   2.154 +
   2.155 +@property (nonatomic, readwrite) bool connected;
   2.156 +@property (nonatomic, readwrite) bool ready;
   2.157 +
   2.158 +@property (nonatomic, strong) CBPeripheral     *bleSteamController;
   2.159 +@property (nonatomic, strong) CBCharacteristic *bleCharacteristicInput;
   2.160 +@property (nonatomic, strong) CBCharacteristic *bleCharacteristicReport;
   2.161 +
   2.162 +- (id)initWithPeripheral:(CBPeripheral *)peripheral;
   2.163 +
   2.164 +@end
   2.165 +
   2.166 +
   2.167 +@interface HIDBLEManager : NSObject <CBCentralManagerDelegate>
   2.168 +
   2.169 +@property (nonatomic) int nPendingScans;
   2.170 +@property (nonatomic) int nPendingPairs;
   2.171 +@property (nonatomic, strong) CBCentralManager *centralManager;
   2.172 +@property (nonatomic, strong) NSMapTable<CBPeripheral *, HIDBLEDevice *> *deviceMap;
   2.173 +@property (nonatomic, retain) dispatch_queue_t bleSerialQueue;
   2.174 +
   2.175 ++ (instancetype)sharedInstance;
   2.176 +- (void)startScan:(int)duration;
   2.177 +- (void)stopScan;
   2.178 +- (int)updateConnectedSteamControllers:(BOOL) bForce;
   2.179 +- (void)appWillResignActiveNotification:(NSNotification *)note;
   2.180 +- (void)appDidBecomeActiveNotification:(NSNotification *)note;
   2.181 +
   2.182 +@end
   2.183 +
   2.184 +
   2.185 +// singleton class - access using HIDBLEManager.sharedInstance
   2.186 +@implementation HIDBLEManager
   2.187 +
   2.188 ++ (instancetype)sharedInstance
   2.189 +{
   2.190 +	static HIDBLEManager *sharedInstance = nil;
   2.191 +	static dispatch_once_t onceToken;
   2.192 +	dispatch_once(&onceToken, ^{
   2.193 +		sharedInstance = [HIDBLEManager new];
   2.194 +		sharedInstance.nPendingScans = 0;
   2.195 +		sharedInstance.nPendingPairs = 0;
   2.196 +		
   2.197 +		[[NSNotificationCenter defaultCenter] addObserver:sharedInstance selector:@selector(appWillResignActiveNotification:) name: UIApplicationWillResignActiveNotification object:nil];
   2.198 +		[[NSNotificationCenter defaultCenter] addObserver:sharedInstance selector:@selector(appDidBecomeActiveNotification:) name:UIApplicationDidBecomeActiveNotification object:nil];
   2.199 +
   2.200 +		// receive reports on a high-priority serial-queue. optionally put writes on the serial queue to avoid logical
   2.201 +		// race conditions talking to the controller from multiple threads, although BLE fragmentation/assembly means
   2.202 +		// that we can still screw this up.
   2.203 +		// most importantly we need to consume reports at a high priority to avoid the OS thinking we aren't really
   2.204 +		// listening to the BLE device, as iOS on slower devices may stop delivery of packets to the app WITHOUT ACTUALLY
   2.205 +		// DISCONNECTING FROM THE DEVICE if we don't react quickly enough to their delivery.
   2.206 +		// see also the error-handling states in the peripheral delegate to re-open the device if it gets closed
   2.207 +		sharedInstance.bleSerialQueue = dispatch_queue_create( "com.valvesoftware.steamcontroller.ble", DISPATCH_QUEUE_SERIAL );
   2.208 +		dispatch_set_target_queue( sharedInstance.bleSerialQueue, dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_HIGH, 0 ) );
   2.209 +
   2.210 +		// creating a CBCentralManager will always trigger a future centralManagerDidUpdateState:
   2.211 +		// where any scanning gets started or connecting to existing peripherals happens, it's never already in a
   2.212 +		// powered-on state for a newly launched application.
   2.213 +		sharedInstance.centralManager = [[CBCentralManager alloc] initWithDelegate:sharedInstance queue:sharedInstance.bleSerialQueue];
   2.214 +		sharedInstance.deviceMap = [[NSMapTable alloc] initWithKeyOptions:NSMapTableWeakMemory valueOptions:NSMapTableStrongMemory capacity:4];
   2.215 +	});
   2.216 +	return sharedInstance;
   2.217 +}
   2.218 +
   2.219 +// called for NSNotification UIApplicationWillResignActiveNotification
   2.220 +- (void)appWillResignActiveNotification:(NSNotification *)note
   2.221 +{
   2.222 +	// we'll get resign-active notification if pairing is happening.
   2.223 +	if ( self.nPendingPairs > 0 )
   2.224 +		return;
   2.225 +
   2.226 +	for ( CBPeripheral *peripheral in self.deviceMap )
   2.227 +	{
   2.228 +		HIDBLEDevice *steamController = [self.deviceMap objectForKey:peripheral];
   2.229 +		if ( steamController )
   2.230 +		{
   2.231 +			steamController.connected = NO;
   2.232 +			steamController.ready = NO;
   2.233 +			[self.centralManager cancelPeripheralConnection:peripheral];
   2.234 +		}
   2.235 +	}
   2.236 +	[self.deviceMap removeAllObjects];
   2.237 +}
   2.238 +
   2.239 +// called for NSNotification UIApplicationDidBecomeActiveNotification
   2.240 +//  whenever the application comes back from being inactive, trigger a 20s pairing scan and reconnect
   2.241 +//  any devices that may have paired while we were inactive.
   2.242 +- (void)appDidBecomeActiveNotification:(NSNotification *)note
   2.243 +{
   2.244 +	[self updateConnectedSteamControllers:true];
   2.245 +	[self startScan:20];
   2.246 +}
   2.247 +
   2.248 +- (int)updateConnectedSteamControllers:(BOOL) bForce
   2.249 +{
   2.250 +	static uint64_t s_unLastUpdateTick = 0;
   2.251 +	static mach_timebase_info_data_t s_timebase_info;
   2.252 +	
   2.253 +	if (s_timebase_info.denom == 0)
   2.254 +	{
   2.255 +		mach_timebase_info( &s_timebase_info );
   2.256 +	}
   2.257 +	
   2.258 +	uint64_t ticksNow = mach_approximate_time();
   2.259 +	if ( !bForce && ( ( (ticksNow - s_unLastUpdateTick) * s_timebase_info.numer ) / s_timebase_info.denom ) < (5ull * NSEC_PER_SEC) )
   2.260 +		return (int)self.deviceMap.count;
   2.261 +	
   2.262 +	// we can see previously connected BLE peripherals but can't connect until the CBCentralManager
   2.263 +	// is fully powered up - only do work when we are in that state
   2.264 +	if ( self.centralManager.state != CBManagerStatePoweredOn )
   2.265 +		return (int)self.deviceMap.count;
   2.266 +
   2.267 +	// only update our last-check-time if we actually did work, otherwise there can be a long delay during initial power-up
   2.268 +	s_unLastUpdateTick = mach_approximate_time();
   2.269 +	
   2.270 +	// if a pair is in-flight, the central manager may still give it back via retrieveConnected... and
   2.271 +	// cause the SDL layer to attempt to initialize it while some of its endpoints haven't yet been established
   2.272 +	if ( self.nPendingPairs > 0 )
   2.273 +		return (int)self.deviceMap.count;
   2.274 +
   2.275 +	NSArray<CBPeripheral *> *peripherals = [self.centralManager retrieveConnectedPeripheralsWithServices: @[ [CBUUID UUIDWithString:@"180A"]]];
   2.276 +	for ( CBPeripheral *peripheral in peripherals )
   2.277 +	{
   2.278 +		// we already know this peripheral
   2.279 +		if ( [self.deviceMap objectForKey: peripheral] != nil )
   2.280 +			continue;
   2.281 +		
   2.282 +		NSLog( @"connected peripheral: %@", peripheral );
   2.283 +		if ( [peripheral.name isEqualToString:@"SteamController"] )
   2.284 +		{
   2.285 +			HIDBLEDevice *steamController = [[HIDBLEDevice alloc] initWithPeripheral:peripheral];
   2.286 +			[self.deviceMap setObject:steamController forKey:peripheral];
   2.287 +			[self.centralManager connectPeripheral:peripheral options:nil];
   2.288 +		}
   2.289 +	}
   2.290 +
   2.291 +	return (int)self.deviceMap.count;
   2.292 +}
   2.293 +
   2.294 +// manual API for folks to start & stop scanning
   2.295 +- (void)startScan:(int)duration
   2.296 +{
   2.297 +	NSLog( @"BLE: requesting scan for %d seconds", duration );
   2.298 +	@synchronized (self)
   2.299 +	{
   2.300 +		if ( _nPendingScans++ == 0 )
   2.301 +		{
   2.302 +			[self.centralManager scanForPeripheralsWithServices:nil options:nil];
   2.303 +		}
   2.304 +	}
   2.305 +
   2.306 +	if ( duration != 0 )
   2.307 +	{
   2.308 +		dispatch_after( dispatch_time( DISPATCH_TIME_NOW, (int64_t)(duration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
   2.309 +			[self stopScan];
   2.310 +		});
   2.311 +	}
   2.312 +}
   2.313 +
   2.314 +- (void)stopScan
   2.315 +{
   2.316 +	NSLog( @"BLE: stopping scan" );
   2.317 +	@synchronized (self)
   2.318 +	{
   2.319 +		if ( --_nPendingScans <= 0 )
   2.320 +		{
   2.321 +			_nPendingScans = 0;
   2.322 +			[self.centralManager stopScan];
   2.323 +		}
   2.324 +	}
   2.325 +}
   2.326 +
   2.327 +
   2.328 +#pragma mark CBCentralManagerDelegate Implementation
   2.329 +
   2.330 +// called whenever the BLE hardware state changes.
   2.331 +- (void)centralManagerDidUpdateState:(CBCentralManager *)central
   2.332 +{
   2.333 +	switch ( central.state )
   2.334 +	{
   2.335 +		case CBCentralManagerStatePoweredOn:
   2.336 +		{
   2.337 +			NSLog( @"CoreBluetooth BLE hardware is powered on and ready" );
   2.338 +			
   2.339 +			// at startup, if we have no already attached peripherals, do a 20s scan for new unpaired devices,
   2.340 +			// otherwise callers should occaisionally do additional scans. we don't want to continuously be
   2.341 +			// scanning because it drains battery, causes other nearby people to have a hard time pairing their
   2.342 +			// Steam Controllers, and may also trigger firmware weirdness when a device attempts to start
   2.343 +			// the pairing sequence multiple times concurrently
   2.344 +			if ( [self updateConnectedSteamControllers:false] == 0 )
   2.345 +			{
   2.346 +				// TODO: we could limit our scan to only peripherals supporting the SteamController service, but
   2.347 +				//  that service doesn't currently fit in the base advertising packet, we'd need to put it into an
   2.348 +				//  extended scan packet. Useful optimization downstream, but not currently necessary
   2.349 +				//	NSArray *services = @[[CBUUID UUIDWithString:VALVE_SERVICE]];
   2.350 +				[self startScan:20];
   2.351 +			}
   2.352 +			break;
   2.353 +		}
   2.354 +			
   2.355 +		case CBCentralManagerStatePoweredOff:
   2.356 +			NSLog( @"CoreBluetooth BLE hardware is powered off" );
   2.357 +			break;
   2.358 +			
   2.359 +		case CBCentralManagerStateUnauthorized:
   2.360 +			NSLog( @"CoreBluetooth BLE state is unauthorized" );
   2.361 +			break;
   2.362 +			
   2.363 +		case CBCentralManagerStateUnknown:
   2.364 +			NSLog( @"CoreBluetooth BLE state is unknown" );
   2.365 +			break;
   2.366 +			
   2.367 +		case CBCentralManagerStateUnsupported:
   2.368 +			NSLog( @"CoreBluetooth BLE hardware is unsupported on this platform" );
   2.369 +			break;
   2.370 +		
   2.371 +		case CBCentralManagerStateResetting:
   2.372 +			NSLog( @"CoreBluetooth BLE manager is resetting" );
   2.373 +			break;
   2.374 +	}
   2.375 +}
   2.376 +
   2.377 +- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
   2.378 +{
   2.379 +	HIDBLEDevice *steamController = [_deviceMap objectForKey:peripheral];
   2.380 +	steamController.connected = YES;
   2.381 +	self.nPendingPairs -= 1;
   2.382 +}
   2.383 +
   2.384 +- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
   2.385 +{
   2.386 +	NSLog( @"Failed to connect: %@", error );
   2.387 +	[_deviceMap removeObjectForKey:peripheral];
   2.388 +	self.nPendingPairs -= 1;
   2.389 +}
   2.390 +
   2.391 +- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
   2.392 +{
   2.393 +	NSString *localName = [advertisementData objectForKey:CBAdvertisementDataLocalNameKey];
   2.394 +	NSString *log = [NSString stringWithFormat:@"Found '%@'", localName];
   2.395 +	
   2.396 +	if ( [localName isEqualToString:@"SteamController"] )
   2.397 +	{
   2.398 +		NSLog( @"%@ : %@ - %@", log, peripheral, advertisementData );
   2.399 +		self.nPendingPairs += 1;
   2.400 +		HIDBLEDevice *steamController = [[HIDBLEDevice alloc] initWithPeripheral:peripheral];
   2.401 +		[self.deviceMap setObject:steamController forKey:peripheral];
   2.402 +		[self.centralManager connectPeripheral:peripheral options:nil];
   2.403 +	}
   2.404 +}
   2.405 +
   2.406 +- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
   2.407 +{
   2.408 +	HIDBLEDevice *steamController = [self.deviceMap objectForKey:peripheral];
   2.409 +	if ( steamController )
   2.410 +	{
   2.411 +		steamController.connected = NO;
   2.412 +		steamController.ready = NO;
   2.413 +		[self.deviceMap removeObjectForKey:peripheral];
   2.414 +	}
   2.415 +}
   2.416 +
   2.417 +@end
   2.418 +
   2.419 +
   2.420 +// Core Bluetooth devices calling back on event boundaries of their run-loops. so annoying.
   2.421 +static void process_pending_events()
   2.422 +{
   2.423 +	CFRunLoopRunResult res;
   2.424 +	do
   2.425 +	{
   2.426 +		res = CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0.001, FALSE );
   2.427 +	}
   2.428 +	while( res != kCFRunLoopRunFinished && res != kCFRunLoopRunTimedOut );
   2.429 +}
   2.430 +
   2.431 +@implementation HIDBLEDevice
   2.432 +
   2.433 +- (id)init
   2.434 +{
   2.435 +	if ( self = [super init] )
   2.436 +	{
   2.437 +        RingBuffer_init( &_inputReports );
   2.438 +		self.bleSteamController = nil;
   2.439 +		self.bleCharacteristicInput = nil;
   2.440 +		self.bleCharacteristicReport = nil;
   2.441 +		_connected = NO;
   2.442 +		_ready = NO;
   2.443 +	}
   2.444 +	return self;
   2.445 +}
   2.446 +
   2.447 +- (id)initWithPeripheral:(CBPeripheral *)peripheral
   2.448 +{
   2.449 +	if ( self = [super init] )
   2.450 +	{
   2.451 +        RingBuffer_init( &_inputReports );
   2.452 +		_connected = NO;
   2.453 +		_ready = NO;
   2.454 +		self.bleSteamController = peripheral;
   2.455 +		if ( peripheral )
   2.456 +		{
   2.457 +			peripheral.delegate = self;
   2.458 +		}
   2.459 +		self.bleCharacteristicInput = nil;
   2.460 +		self.bleCharacteristicReport = nil;
   2.461 +	}
   2.462 +	return self;
   2.463 +}
   2.464 +
   2.465 +- (void)setConnected:(bool)connected
   2.466 +{
   2.467 +	_connected = connected;
   2.468 +	if ( _connected )
   2.469 +	{
   2.470 +		[_bleSteamController discoverServices:nil];
   2.471 +	}
   2.472 +	else
   2.473 +	{
   2.474 +		NSLog( @"Disconnected" );
   2.475 +	}
   2.476 +}
   2.477 +
   2.478 +- (size_t)read_input_report:(uint8_t *)dst
   2.479 +{
   2.480 +	if ( RingBuffer_read( &_inputReports, dst+1 ) )
   2.481 +	{
   2.482 +		*dst = 0x03;
   2.483 +		return 20;
   2.484 +	}
   2.485 +	return 0;
   2.486 +}
   2.487 +
   2.488 +- (int)send_report:(const uint8_t *)data length:(size_t)length
   2.489 +{
   2.490 +	[_bleSteamController writeValue:[NSData dataWithBytes:data length:length] forCharacteristic:_bleCharacteristicReport type:CBCharacteristicWriteWithResponse];
   2.491 +	return (int)length;
   2.492 +}
   2.493 +
   2.494 +- (int)send_feature_report:(hidFeatureReport *)report
   2.495 +{
   2.496 +#if FEATURE_REPORT_LOGGING
   2.497 +	uint8_t *reportBytes = (uint8_t *)report;
   2.498 +	
   2.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 ),
   2.500 +		  reportBytes[1], reportBytes[2], reportBytes[3], reportBytes[4], reportBytes[5], reportBytes[6],
   2.501 +		  reportBytes[7], reportBytes[8], reportBytes[9], reportBytes[10], reportBytes[11], reportBytes[12],
   2.502 +		  reportBytes[13], reportBytes[14], reportBytes[15], reportBytes[16], reportBytes[17], reportBytes[18],
   2.503 +		  reportBytes[19] );
   2.504 +#endif
   2.505 +
   2.506 +	int sendSize = (int)GetBluetoothSegmentSize( &report->segment );
   2.507 +	if ( sendSize > 20 )
   2.508 +		sendSize = 20;
   2.509 +
   2.510 +#if 1
   2.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,
   2.512 +	//  except errors.
   2.513 +	[_bleSteamController writeValue:[NSData dataWithBytes:&report->segment length:sendSize] forCharacteristic:_bleCharacteristicReport type:CBCharacteristicWriteWithResponse];
   2.514 +	
   2.515 +	// pretend we received a result anybody cares about
   2.516 +	return 19;
   2.517 +
   2.518 +#else
   2.519 +	// this is technically the correct send_feature_report logic if you want to make sure it gets through and is
   2.520 +	// acknowledged or errors out
   2.521 +	_waitStateForWriteFeatureReport = BLEDeviceWaitState_Waiting;
   2.522 +	[_bleSteamController writeValue:[NSData dataWithBytes:&report->segment length:sendSize
   2.523 +									 ] forCharacteristic:_bleCharacteristicReport type:CBCharacteristicWriteWithResponse];
   2.524 +	
   2.525 +	while ( _waitStateForWriteFeatureReport == BLEDeviceWaitState_Waiting )
   2.526 +	{
   2.527 +		process_pending_events();
   2.528 +	}
   2.529 +	
   2.530 +	if ( _waitStateForWriteFeatureReport == BLEDeviceWaitState_Error )
   2.531 +	{
   2.532 +		_waitStateForWriteFeatureReport = BLEDeviceWaitState_None;
   2.533 +		return -1;
   2.534 +	}
   2.535 +	
   2.536 +	_waitStateForWriteFeatureReport = BLEDeviceWaitState_None;
   2.537 +	return 19;
   2.538 +#endif
   2.539 +}
   2.540 +
   2.541 +- (int)get_feature_report:(uint8_t)feature into:(uint8_t *)buffer
   2.542 +{
   2.543 +	_waitStateForReadFeatureReport = BLEDeviceWaitState_Waiting;
   2.544 +	[_bleSteamController readValueForCharacteristic:_bleCharacteristicReport];
   2.545 +	
   2.546 +	while ( _waitStateForReadFeatureReport == BLEDeviceWaitState_Waiting )
   2.547 +		process_pending_events();
   2.548 +	
   2.549 +	if ( _waitStateForReadFeatureReport == BLEDeviceWaitState_Error )
   2.550 +	{
   2.551 +		_waitStateForReadFeatureReport = BLEDeviceWaitState_None;
   2.552 +		return -1;
   2.553 +	}
   2.554 +	
   2.555 +	memcpy( buffer, _featureReport, sizeof(_featureReport) );
   2.556 +	
   2.557 +	_waitStateForReadFeatureReport = BLEDeviceWaitState_None;
   2.558 +	
   2.559 +#if FEATURE_REPORT_LOGGING
   2.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]",
   2.561 +		  buffer[1], buffer[2], buffer[3], buffer[4], buffer[5], buffer[6],
   2.562 +		  buffer[7], buffer[8], buffer[9], buffer[10], buffer[11], buffer[12],
   2.563 +		  buffer[13], buffer[14], buffer[15], buffer[16], buffer[17], buffer[18],
   2.564 +		  buffer[19] );
   2.565 +#endif
   2.566 +
   2.567 +	return 19;
   2.568 +}
   2.569 +
   2.570 +#pragma mark CBPeripheralDelegate Implementation
   2.571 +
   2.572 +- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error
   2.573 +{
   2.574 +	for (CBService *service in peripheral.services)
   2.575 +	{
   2.576 +		NSLog( @"Found Service: %@", service );
   2.577 +		if ( [service.UUID isEqual:[CBUUID UUIDWithString:VALVE_SERVICE]] )
   2.578 +		{
   2.579 +			[peripheral discoverCharacteristics:nil forService:service];
   2.580 +		}
   2.581 +	}
   2.582 +}
   2.583 +
   2.584 +- (void)peripheral:(CBPeripheral *)peripheral didDiscoverDescriptorsForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
   2.585 +{
   2.586 +	// nothing yet needed here, enable for logging
   2.587 +	if ( /* DISABLES CODE */ (0) )
   2.588 +	{
   2.589 +		for ( CBDescriptor *descriptor in characteristic.descriptors )
   2.590 +		{
   2.591 +			NSLog( @" - Descriptor '%@'", descriptor );
   2.592 +		}
   2.593 +	}
   2.594 +}
   2.595 +
   2.596 +- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
   2.597 +{
   2.598 +	if ([service.UUID isEqual:[CBUUID UUIDWithString:VALVE_SERVICE]])
   2.599 +	{
   2.600 +		for (CBCharacteristic *aChar in service.characteristics)
   2.601 +		{
   2.602 +			NSLog( @"Found Characteristic %@", aChar );
   2.603 +			
   2.604 +			if ( [aChar.UUID isEqual:[CBUUID UUIDWithString:VALVE_INPUT_CHAR]] )
   2.605 +			{
   2.606 +				self.bleCharacteristicInput = aChar;
   2.607 +			}
   2.608 +			else if ( [aChar.UUID isEqual:[CBUUID UUIDWithString:VALVE_REPORT_CHAR]] )
   2.609 +			{
   2.610 +				self.bleCharacteristicReport = aChar;
   2.611 +				[self.bleSteamController discoverDescriptorsForCharacteristic: aChar];
   2.612 +			}
   2.613 +		}
   2.614 +	}
   2.615 +}
   2.616 +
   2.617 +- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
   2.618 +{
   2.619 +	static uint64_t s_ticksLastOverflowReport = 0;
   2.620 +
   2.621 +	// receiving an input report is the final indicator that the user accepted a pairing
   2.622 +	// request and that we successfully established notification. CoreBluetooth has no
   2.623 +	// notification of the pairing acknowledgement, which is a bad oversight.
   2.624 +	if ( self.ready == NO )
   2.625 +	{
   2.626 +		self.ready = YES;
   2.627 +		HIDBLEManager.sharedInstance.nPendingPairs -= 1;
   2.628 +	}
   2.629 +
   2.630 +	if ( [characteristic.UUID isEqual:_bleCharacteristicInput.UUID] )
   2.631 +	{
   2.632 +		NSData *data = [characteristic value];
   2.633 +		if ( data.length != 19 )
   2.634 +		{
   2.635 +			NSLog( @"HIDBLE: incoming data is %lu bytes should be exactly 19", (unsigned long)data.length );
   2.636 +		}
   2.637 +		if ( !RingBuffer_write( &_inputReports, (const uint8_t *)data.bytes ) )
   2.638 +		{
   2.639 +			uint64_t ticksNow = mach_approximate_time();
   2.640 +			if ( ticksNow - s_ticksLastOverflowReport > (5ull * NSEC_PER_SEC / 10) )
   2.641 +			{
   2.642 +				NSLog( @"HIDBLE: input report buffer overflow" );
   2.643 +				s_ticksLastOverflowReport = ticksNow;
   2.644 +			}
   2.645 +		}
   2.646 +	}
   2.647 +	else if ( [characteristic.UUID isEqual:_bleCharacteristicReport.UUID] )
   2.648 +	{
   2.649 +		memset( _featureReport, 0, sizeof(_featureReport) );
   2.650 +		
   2.651 +		if ( error != nil )
   2.652 +		{
   2.653 +			NSLog( @"HIDBLE: get_feature_report error: %@", error );
   2.654 +			_waitStateForReadFeatureReport = BLEDeviceWaitState_Error;
   2.655 +		}
   2.656 +		else
   2.657 +		{
   2.658 +			NSData *data = [characteristic value];
   2.659 +			if ( data.length != 20 )
   2.660 +			{
   2.661 +				NSLog( @"HIDBLE: incoming data is %lu bytes should be exactly 20", (unsigned long)data.length );
   2.662 +			}
   2.663 +			memcpy( _featureReport, data.bytes, MIN( data.length, sizeof(_featureReport) ) );
   2.664 +			_waitStateForReadFeatureReport = BLEDeviceWaitState_Complete;
   2.665 +		}
   2.666 +	}
   2.667 +}
   2.668 +
   2.669 +- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
   2.670 +{
   2.671 +	if ( [characteristic.UUID isEqual:[CBUUID UUIDWithString:VALVE_REPORT_CHAR]] )
   2.672 +	{
   2.673 +		if ( error != nil )
   2.674 +		{
   2.675 +			NSLog( @"HIDBLE: write_feature_report error: %@", error );
   2.676 +			_waitStateForWriteFeatureReport = BLEDeviceWaitState_Error;
   2.677 +		}
   2.678 +		else
   2.679 +		{
   2.680 +			_waitStateForWriteFeatureReport = BLEDeviceWaitState_Complete;
   2.681 +		}
   2.682 +	}
   2.683 +}
   2.684 +
   2.685 +- (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
   2.686 +{
   2.687 +	NSLog( @"didUpdateNotifcationStateForCharacteristic %@ (%@)", characteristic, error );
   2.688 +}
   2.689 +
   2.690 +@end
   2.691 +
   2.692 +
   2.693 +#pragma mark hid_api implementation
   2.694 +
   2.695 +struct hid_device_ {
   2.696 +	void *device_handle;
   2.697 +	int blocking;
   2.698 +	hid_device *next;
   2.699 +};
   2.700 +
   2.701 +int HID_API_EXPORT HID_API_CALL hid_init(void)
   2.702 +{
   2.703 +	return ( HIDBLEManager.sharedInstance == nil ) ? -1 : 0;
   2.704 +}
   2.705 +
   2.706 +int HID_API_EXPORT HID_API_CALL hid_exit(void)
   2.707 +{
   2.708 +	return 0;
   2.709 +}
   2.710 +
   2.711 +void HID_API_EXPORT HID_API_CALL hid_ble_scan( bool bStart )
   2.712 +{
   2.713 +	HIDBLEManager *bleManager = HIDBLEManager.sharedInstance;
   2.714 +	if ( bStart )
   2.715 +	{
   2.716 +		[bleManager startScan:0];
   2.717 +	}
   2.718 +	else
   2.719 +	{
   2.720 +		[bleManager stopScan];
   2.721 +	}
   2.722 +}
   2.723 +
   2.724 +hid_device * HID_API_EXPORT hid_open_path( const char *path, int bExclusive /* = false */ )
   2.725 +{
   2.726 +	hid_device *result = NULL;
   2.727 +	NSString *nssPath = [NSString stringWithUTF8String:path];
   2.728 +	HIDBLEManager *bleManager = HIDBLEManager.sharedInstance;
   2.729 +	NSEnumerator<HIDBLEDevice *> *devices = [bleManager.deviceMap objectEnumerator];
   2.730 +	
   2.731 +	for ( HIDBLEDevice *device in devices )
   2.732 +	{
   2.733 +		// we have the device but it hasn't found its service or characteristics until it is connected
   2.734 +		if ( !device.ready || !device.connected || !device.bleCharacteristicInput )
   2.735 +			continue;
   2.736 +		
   2.737 +		if ( [device.bleSteamController.identifier.UUIDString isEqualToString:nssPath] )
   2.738 +		{
   2.739 +			result = (hid_device *)malloc( sizeof( hid_device ) );
   2.740 +			memset( result, 0, sizeof( hid_device ) );
   2.741 +			result->device_handle = (void*)CFBridgingRetain( device );
   2.742 +			result->blocking = NO;
   2.743 +			// enable reporting input events on the characteristic
   2.744 +			[device.bleSteamController setNotifyValue:YES forCharacteristic:device.bleCharacteristicInput];
   2.745 +			return result;
   2.746 +		}
   2.747 +	}
   2.748 +	return result;
   2.749 +}
   2.750 +
   2.751 +void  HID_API_EXPORT hid_free_enumeration(struct hid_device_info *devs)
   2.752 +{
   2.753 +	/* This function is identical to the Linux version. Platform independent. */
   2.754 +	struct hid_device_info *d = devs;
   2.755 +	while (d) {
   2.756 +		struct hid_device_info *next = d->next;
   2.757 +		free(d->path);
   2.758 +		free(d->serial_number);
   2.759 +		free(d->manufacturer_string);
   2.760 +		free(d->product_string);
   2.761 +		free(d);
   2.762 +		d = next;
   2.763 +	}
   2.764 +}
   2.765 +
   2.766 +int HID_API_EXPORT hid_set_nonblocking(hid_device *dev, int nonblock)
   2.767 +{
   2.768 +	/* All Nonblocking operation is handled by the library. */
   2.769 +	dev->blocking = !nonblock;
   2.770 +	
   2.771 +	return 0;
   2.772 +}
   2.773 +
   2.774 +struct hid_device_info  HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, unsigned short product_id)
   2.775 +{ @autoreleasepool {
   2.776 +	struct hid_device_info *root = NULL;
   2.777 +	
   2.778 +	if ( ( vendor_id == 0 && product_id == 0 ) ||
   2.779 +		 ( vendor_id == VALVE_USB_VID && product_id == D0G_BLE2_PID ) )
   2.780 +	{
   2.781 +		HIDBLEManager *bleManager = HIDBLEManager.sharedInstance;
   2.782 +		[bleManager updateConnectedSteamControllers:false];
   2.783 +		NSEnumerator<HIDBLEDevice *> *devices = [bleManager.deviceMap objectEnumerator];
   2.784 +		for ( HIDBLEDevice *device in devices )
   2.785 +		{
   2.786 +			// there are several brief windows in connecting to an already paired device and
   2.787 +			// one long window waiting for users to confirm pairing where we don't want
   2.788 +			// to consider a device ready - if we hand it back to SDL or another
   2.789 +			// Steam Controller consumer, their additional SC setup work will fail
   2.790 +			// in unusual/silent ways and we can actually corrupt the BLE stack for
   2.791 +			// the entire system and kill the appletv remote's Menu button (!)
   2.792 +			if ( device.bleSteamController.state != CBPeripheralStateConnected ||
   2.793 +				 device.connected == NO || device.ready == NO )
   2.794 +			{
   2.795 +				if ( device.ready == NO && device.bleCharacteristicInput != nil )
   2.796 +				{
   2.797 +					// attempt to register for input reports. this call will silently fail
   2.798 +					// until the pairing finalizes with user acceptance. oh, apple.
   2.799 +					[device.bleSteamController setNotifyValue:YES forCharacteristic:device.bleCharacteristicInput];
   2.800 +				}
   2.801 +				continue;
   2.802 +			}
   2.803 +			struct hid_device_info *device_info = (struct hid_device_info *)malloc( sizeof(struct hid_device_info) );
   2.804 +			memset( device_info, 0, sizeof(struct hid_device_info) );
   2.805 +			device_info->next = root;
   2.806 +			root = device_info;
   2.807 +			device_info->path = strdup( device.bleSteamController.identifier.UUIDString.UTF8String );
   2.808 +			device_info->vendor_id = VALVE_USB_VID;
   2.809 +			device_info->product_id = D0G_BLE2_PID;
   2.810 +			device_info->product_string = wcsdup( L"Steam Controller" );
   2.811 +			device_info->manufacturer_string = wcsdup( L"Valve Corporation" );
   2.812 +		}
   2.813 +	}
   2.814 +	return root;
   2.815 +}}
   2.816 +
   2.817 +int HID_API_EXPORT_CALL hid_get_manufacturer_string(hid_device *dev, wchar_t *string, size_t maxlen)
   2.818 +{
   2.819 +	static wchar_t s_wszManufacturer[] = L"Valve Corporation";
   2.820 +	wcsncpy( string, s_wszManufacturer, sizeof(s_wszManufacturer)/sizeof(s_wszManufacturer[0]) );
   2.821 +	return 0;
   2.822 +}
   2.823 +
   2.824 +int HID_API_EXPORT_CALL hid_get_product_string(hid_device *dev, wchar_t *string, size_t maxlen)
   2.825 +{
   2.826 +	static wchar_t s_wszProduct[] = L"Steam Controller";
   2.827 +	wcsncpy( string, s_wszProduct, sizeof(s_wszProduct)/sizeof(s_wszProduct[0]) );
   2.828 +	return 0;
   2.829 +}
   2.830 +
   2.831 +int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen)
   2.832 +{
   2.833 +	static wchar_t s_wszSerial[] = L"12345";
   2.834 +	wcsncpy( string, s_wszSerial, sizeof(s_wszSerial)/sizeof(s_wszSerial[0]) );
   2.835 +	return 0;
   2.836 +}
   2.837 +
   2.838 +int HID_API_EXPORT hid_write(hid_device *dev, const unsigned char *data, size_t length)
   2.839 +{
   2.840 +    HIDBLEDevice *device_handle = (__bridge HIDBLEDevice *)dev->device_handle;
   2.841 +
   2.842 +	if ( !device_handle.connected )
   2.843 +		return -1;
   2.844 +
   2.845 +	return [device_handle send_report:data length:length];
   2.846 +}
   2.847 +
   2.848 +void HID_API_EXPORT hid_close(hid_device *dev)
   2.849 +{
   2.850 +    HIDBLEDevice *device_handle = CFBridgingRelease( dev->device_handle );
   2.851 +
   2.852 +	// disable reporting input events on the characteristic
   2.853 +	if ( device_handle.connected ) {
   2.854 +		[device_handle.bleSteamController setNotifyValue:NO forCharacteristic:device_handle.bleCharacteristicInput];
   2.855 +	}
   2.856 +
   2.857 +	free( dev );
   2.858 +}
   2.859 +
   2.860 +int HID_API_EXPORT hid_send_feature_report(hid_device *dev, const unsigned char *data, size_t length)
   2.861 +{
   2.862 +    HIDBLEDevice *device_handle = (__bridge HIDBLEDevice *)dev->device_handle;
   2.863 +
   2.864 +	if ( !device_handle.connected )
   2.865 +		return -1;
   2.866 +
   2.867 +	return [device_handle send_feature_report:(hidFeatureReport *)(void *)data];
   2.868 +}
   2.869 +
   2.870 +int HID_API_EXPORT hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length)
   2.871 +{
   2.872 +    HIDBLEDevice *device_handle = (__bridge HIDBLEDevice *)dev->device_handle;
   2.873 +
   2.874 +	if ( !device_handle.connected )
   2.875 +		return -1;
   2.876 +
   2.877 +	size_t written = [device_handle get_feature_report:data[0] into:data];
   2.878 +	
   2.879 +	return written == length-1 ? (int)length : (int)written;
   2.880 +}
   2.881 +
   2.882 +int HID_API_EXPORT hid_read(hid_device *dev, unsigned char *data, size_t length)
   2.883 +{
   2.884 +    HIDBLEDevice *device_handle = (__bridge HIDBLEDevice *)dev->device_handle;
   2.885 +
   2.886 +	if ( !device_handle.connected )
   2.887 +		return -1;
   2.888 +
   2.889 +	return hid_read_timeout(dev, data, length, 0);
   2.890 +}
   2.891 +
   2.892 +int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t length, int milliseconds)
   2.893 +{
   2.894 +    HIDBLEDevice *device_handle = (__bridge HIDBLEDevice *)dev->device_handle;
   2.895 +
   2.896 +	if ( !device_handle.connected )
   2.897 +		return -1;
   2.898 +	
   2.899 +	if ( milliseconds != 0 )
   2.900 +	{
   2.901 +		NSLog( @"hid_read_timeout with non-zero wait" );
   2.902 +	}
   2.903 +	int result = (int)[device_handle read_input_report:data];
   2.904 +#if FEATURE_REPORT_LOGGING
   2.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,
   2.906 +		  data[1], data[2], data[3], data[4], data[5], data[6],
   2.907 +		  data[7], data[8], data[9], data[10], data[11], data[12],
   2.908 +		  data[13], data[14], data[15], data[16], data[17], data[18],
   2.909 +		  data[19] );
   2.910 +#endif
   2.911 +	return result;
   2.912 +}
     3.1 --- a/src/hidapi/ios/hid.mm	Tue Aug 21 17:24:12 2018 -0700
     3.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
     3.3 @@ -1,883 +0,0 @@
     3.4 -//======== Copyright (c) 2017 Valve Corporation, All rights reserved. =========
     3.5 -//
     3.6 -// Purpose: HID device abstraction temporary stub
     3.7 -//
     3.8 -//=============================================================================
     3.9 -
    3.10 -#include <CoreBluetooth/CoreBluetooth.h>
    3.11 -#include <QuartzCore/QuartzCore.h>
    3.12 -#import <UIKit/UIKit.h>
    3.13 -#import <mach/mach_time.h>
    3.14 -#include <pthread.h>
    3.15 -#include <sys/time.h>
    3.16 -#include <unistd.h>
    3.17 -#include "../hidapi/hidapi.h"
    3.18 -
    3.19 -#define VALVE_USB_VID       0x28DE
    3.20 -#define D0G_BLE2_PID        0x1106
    3.21 -
    3.22 -typedef uint32_t uint32;
    3.23 -typedef uint64_t uint64;
    3.24 -
    3.25 -// enables detailed NSLog logging of feature reports
    3.26 -#define FEATURE_REPORT_LOGGING	0
    3.27 -
    3.28 -#define REPORT_SEGMENT_DATA_FLAG	0x80
    3.29 -#define REPORT_SEGMENT_LAST_FLAG	0x40
    3.30 -
    3.31 -#define VALVE_SERVICE		@"100F6C32-1735-4313-B402-38567131E5F3"
    3.32 -
    3.33 -// (READ/NOTIFICATIONS)
    3.34 -#define VALVE_INPUT_CHAR	@"100F6C33-1735-4313-B402-38567131E5F3"
    3.35 -
    3.36 -//  (READ/WRITE)
    3.37 -#define VALVE_REPORT_CHAR	@"100F6C34-1735-4313-B402-38567131E5F3"
    3.38 -
    3.39 -// TODO: create CBUUID's in __attribute__((constructor)) rather than doing [CBUUID UUIDWithString:...] everywhere
    3.40 -
    3.41 -#pragma pack(push,1)
    3.42 -struct bluetoothSegment {
    3.43 -	uint8_t		segmentHeader;
    3.44 -	uint8_t		featureReportMessageID;
    3.45 -	uint8_t		length;
    3.46 -	uint8_t		settingIdentifier;
    3.47 -	union {
    3.48 -		uint16_t	usPayload;
    3.49 -		uint32_t	uPayload;
    3.50 -		uint64_t	ulPayload;
    3.51 -		uint8_t		ucPayload[15];
    3.52 -	};
    3.53 -	
    3.54 -	size_t size() { return length + 3; }
    3.55 -};
    3.56 -
    3.57 -struct hidFeatureReport {
    3.58 -	uint8_t		id;
    3.59 -	union {
    3.60 -		bluetoothSegment segment;
    3.61 -		struct {
    3.62 -			uint8_t		segmentHeader;
    3.63 -			uint8_t		featureReportMessageID;
    3.64 -			uint8_t		length;
    3.65 -			uint8_t		settingIdentifier;
    3.66 -			union {
    3.67 -				uint16_t	usPayload;
    3.68 -				uint32_t	uPayload;
    3.69 -				uint64_t	ulPayload;
    3.70 -				uint8_t		ucPayload[15];
    3.71 -			};
    3.72 -		};
    3.73 -	};
    3.74 -};
    3.75 -#pragma pack(pop)
    3.76 -
    3.77 -template <typename T, size_t cbElem, size_t nElem>
    3.78 -struct RingBuffer {
    3.79 -	int _first, _last;
    3.80 -	uint8_t _data[ ( nElem * cbElem ) ];
    3.81 -	pthread_mutex_t accessLock;
    3.82 -	
    3.83 -	RingBuffer() { _first = -1; _last = 0; pthread_mutex_init( &accessLock, 0 ); }
    3.84 -	
    3.85 -	bool write( const T *src )
    3.86 -	{
    3.87 -		pthread_mutex_lock( &accessLock );
    3.88 -		memcpy( &_data[ _last ], src, cbElem );
    3.89 -		if ( _first == -1 )
    3.90 -		{
    3.91 -			_first = _last;
    3.92 -		}
    3.93 -		_last = ( _last + cbElem ) % (nElem * cbElem);
    3.94 -		if ( _last == _first )
    3.95 -		{
    3.96 -			_first = ( _first + cbElem ) % (nElem * cbElem);
    3.97 -			pthread_mutex_unlock( &accessLock );
    3.98 -			return false;
    3.99 -		}
   3.100 -		pthread_mutex_unlock( &accessLock );
   3.101 -		return true;
   3.102 -	}
   3.103 -	
   3.104 -	bool read( T *dst )
   3.105 -	{
   3.106 -		pthread_mutex_lock( &accessLock );
   3.107 -		if ( _first == -1 )
   3.108 -		{
   3.109 -			pthread_mutex_unlock( &accessLock );
   3.110 -			return false;
   3.111 -		}
   3.112 -		memcpy( dst, &_data[ _first ], cbElem );
   3.113 -		_first = ( _first + cbElem ) % (nElem * cbElem);
   3.114 -		if ( _first == _last )
   3.115 -		{
   3.116 -			_first = -1;
   3.117 -		}
   3.118 -		pthread_mutex_unlock( &accessLock );
   3.119 -		return true;
   3.120 -	}
   3.121 -	
   3.122 -};
   3.123 -
   3.124 -
   3.125 -#pragma mark HIDBLEDevice Definition
   3.126 -
   3.127 -enum BLEDeviceWaitState
   3.128 -{
   3.129 -	None,
   3.130 -	Waiting,
   3.131 -	Complete,
   3.132 -	Error
   3.133 -};
   3.134 -
   3.135 -@interface HIDBLEDevice : NSObject <CBPeripheralDelegate>
   3.136 -{
   3.137 -	RingBuffer<uint8_t, 19, 4096> _inputReports;
   3.138 -	uint8_t		_featureReport[20];
   3.139 -	BLEDeviceWaitState	_waitStateForReadFeatureReport;
   3.140 -	BLEDeviceWaitState	_waitStateForWriteFeatureReport;
   3.141 -}
   3.142 -
   3.143 -@property (nonatomic, readwrite) bool connected;
   3.144 -@property (nonatomic, readwrite) bool ready;
   3.145 -
   3.146 -@property (nonatomic, strong) CBPeripheral     *bleSteamController;
   3.147 -@property (nonatomic, strong) CBCharacteristic *bleCharacteristicInput;
   3.148 -@property (nonatomic, strong) CBCharacteristic *bleCharacteristicReport;
   3.149 -
   3.150 -- (id)initWithPeripheral:(CBPeripheral *)peripheral;
   3.151 -
   3.152 -@end
   3.153 -
   3.154 -
   3.155 -@interface HIDBLEManager : NSObject <CBCentralManagerDelegate>
   3.156 -
   3.157 -@property (nonatomic) int nPendingScans;
   3.158 -@property (nonatomic) int nPendingPairs;
   3.159 -@property (nonatomic, strong) CBCentralManager *centralManager;
   3.160 -@property (nonatomic, strong) NSMapTable<CBPeripheral *, HIDBLEDevice *> *deviceMap;
   3.161 -@property (nonatomic, retain) dispatch_queue_t bleSerialQueue;
   3.162 -
   3.163 -+ (instancetype)sharedInstance;
   3.164 -- (void)startScan:(int)duration;
   3.165 -- (void)stopScan;
   3.166 -- (int)updateConnectedSteamControllers:(BOOL) bForce;
   3.167 -- (void)appWillResignActiveNotification:(NSNotification *)note;
   3.168 -- (void)appDidBecomeActiveNotification:(NSNotification *)note;
   3.169 -
   3.170 -@end
   3.171 -
   3.172 -
   3.173 -// singleton class - access using HIDBLEManager.sharedInstance
   3.174 -@implementation HIDBLEManager
   3.175 -
   3.176 -+ (instancetype)sharedInstance
   3.177 -{
   3.178 -	static HIDBLEManager *sharedInstance = nil;
   3.179 -	static dispatch_once_t onceToken;
   3.180 -	dispatch_once(&onceToken, ^{
   3.181 -		sharedInstance = [HIDBLEManager new];
   3.182 -		sharedInstance.nPendingScans = 0;
   3.183 -		sharedInstance.nPendingPairs = 0;
   3.184 -		
   3.185 -		[[NSNotificationCenter defaultCenter] addObserver:sharedInstance selector:@selector(appWillResignActiveNotification:) name: UIApplicationWillResignActiveNotification object:nil];
   3.186 -		[[NSNotificationCenter defaultCenter] addObserver:sharedInstance selector:@selector(appDidBecomeActiveNotification:) name:UIApplicationDidBecomeActiveNotification object:nil];
   3.187 -
   3.188 -		// receive reports on a high-priority serial-queue. optionally put writes on the serial queue to avoid logical
   3.189 -		// race conditions talking to the controller from multiple threads, although BLE fragmentation/assembly means
   3.190 -		// that we can still screw this up.
   3.191 -		// most importantly we need to consume reports at a high priority to avoid the OS thinking we aren't really
   3.192 -		// listening to the BLE device, as iOS on slower devices may stop delivery of packets to the app WITHOUT ACTUALLY
   3.193 -		// DISCONNECTING FROM THE DEVICE if we don't react quickly enough to their delivery.
   3.194 -		// see also the error-handling states in the peripheral delegate to re-open the device if it gets closed
   3.195 -		sharedInstance.bleSerialQueue = dispatch_queue_create( "com.valvesoftware.steamcontroller.ble", DISPATCH_QUEUE_SERIAL );
   3.196 -		dispatch_set_target_queue( sharedInstance.bleSerialQueue, dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_HIGH, 0 ) );
   3.197 -
   3.198 -		// creating a CBCentralManager will always trigger a future centralManagerDidUpdateState:
   3.199 -		// where any scanning gets started or connecting to existing peripherals happens, it's never already in a
   3.200 -		// powered-on state for a newly launched application.
   3.201 -		sharedInstance.centralManager = [[CBCentralManager alloc] initWithDelegate:sharedInstance queue:sharedInstance.bleSerialQueue];
   3.202 -		sharedInstance.deviceMap = [[NSMapTable alloc] initWithKeyOptions:NSMapTableWeakMemory valueOptions:NSMapTableStrongMemory capacity:4];
   3.203 -	});
   3.204 -	return sharedInstance;
   3.205 -}
   3.206 -
   3.207 -// called for NSNotification UIApplicationWillResignActiveNotification
   3.208 -- (void)appWillResignActiveNotification:(NSNotification *)note
   3.209 -{
   3.210 -	// we'll get resign-active notification if pairing is happening.
   3.211 -	if ( self.nPendingPairs > 0 )
   3.212 -		return;
   3.213 -
   3.214 -	for ( CBPeripheral *peripheral in self.deviceMap )
   3.215 -	{
   3.216 -		HIDBLEDevice *steamController = [self.deviceMap objectForKey:peripheral];
   3.217 -		if ( steamController )
   3.218 -		{
   3.219 -			steamController.connected = NO;
   3.220 -			steamController.ready = NO;
   3.221 -			[self.centralManager cancelPeripheralConnection:peripheral];
   3.222 -		}
   3.223 -	}
   3.224 -	[self.deviceMap removeAllObjects];
   3.225 -}
   3.226 -
   3.227 -// called for NSNotification UIApplicationDidBecomeActiveNotification
   3.228 -//  whenever the application comes back from being inactive, trigger a 20s pairing scan and reconnect
   3.229 -//  any devices that may have paired while we were inactive.
   3.230 -- (void)appDidBecomeActiveNotification:(NSNotification *)note
   3.231 -{
   3.232 -	[self updateConnectedSteamControllers:true];
   3.233 -	[self startScan:20];
   3.234 -}
   3.235 -
   3.236 -- (int)updateConnectedSteamControllers:(BOOL) bForce
   3.237 -{
   3.238 -	static uint64_t s_unLastUpdateTick = 0;
   3.239 -	static mach_timebase_info_data_t s_timebase_info;
   3.240 -	
   3.241 -	if (s_timebase_info.denom == 0)
   3.242 -	{
   3.243 -		mach_timebase_info( &s_timebase_info );
   3.244 -	}
   3.245 -	
   3.246 -	uint64_t ticksNow = mach_approximate_time();
   3.247 -	if ( !bForce && ( ( (ticksNow - s_unLastUpdateTick) * s_timebase_info.numer ) / s_timebase_info.denom ) < (5ull * NSEC_PER_SEC) )
   3.248 -		return (int)self.deviceMap.count;
   3.249 -	
   3.250 -	// we can see previously connected BLE peripherals but can't connect until the CBCentralManager
   3.251 -	// is fully powered up - only do work when we are in that state
   3.252 -	if ( self.centralManager.state != CBManagerStatePoweredOn )
   3.253 -		return (int)self.deviceMap.count;
   3.254 -
   3.255 -	// only update our last-check-time if we actually did work, otherwise there can be a long delay during initial power-up
   3.256 -	s_unLastUpdateTick = mach_approximate_time();
   3.257 -	
   3.258 -	// if a pair is in-flight, the central manager may still give it back via retrieveConnected... and
   3.259 -	// cause the SDL layer to attempt to initialize it while some of its endpoints haven't yet been established
   3.260 -	if ( self.nPendingPairs > 0 )
   3.261 -		return (int)self.deviceMap.count;
   3.262 -
   3.263 -	NSArray<CBPeripheral *> *peripherals = [self.centralManager retrieveConnectedPeripheralsWithServices: @[ [CBUUID UUIDWithString:@"180A"]]];
   3.264 -	for ( CBPeripheral *peripheral in peripherals )
   3.265 -	{
   3.266 -		// we already know this peripheral
   3.267 -		if ( [self.deviceMap objectForKey: peripheral] != nil )
   3.268 -			continue;
   3.269 -		
   3.270 -		NSLog( @"connected peripheral: %@", peripheral );
   3.271 -		if ( [peripheral.name isEqualToString:@"SteamController"] )
   3.272 -		{
   3.273 -			HIDBLEDevice *steamController = [[HIDBLEDevice alloc] initWithPeripheral:peripheral];
   3.274 -			[self.deviceMap setObject:steamController forKey:peripheral];
   3.275 -			[self.centralManager connectPeripheral:peripheral options:nil];
   3.276 -		}
   3.277 -	}
   3.278 -
   3.279 -	return (int)self.deviceMap.count;
   3.280 -}
   3.281 -
   3.282 -// manual API for folks to start & stop scanning
   3.283 -- (void)startScan:(int)duration
   3.284 -{
   3.285 -	NSLog( @"BLE: requesting scan for %d seconds", duration );
   3.286 -	@synchronized (self)
   3.287 -	{
   3.288 -		if ( _nPendingScans++ == 0 )
   3.289 -		{
   3.290 -			[self.centralManager scanForPeripheralsWithServices:nil options:nil];
   3.291 -		}
   3.292 -	}
   3.293 -
   3.294 -	if ( duration != 0 )
   3.295 -	{
   3.296 -		dispatch_after( dispatch_time( DISPATCH_TIME_NOW, (int64_t)(duration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
   3.297 -			[self stopScan];
   3.298 -		});
   3.299 -	}
   3.300 -}
   3.301 -
   3.302 -- (void)stopScan
   3.303 -{
   3.304 -	NSLog( @"BLE: stopping scan" );
   3.305 -	@synchronized (self)
   3.306 -	{
   3.307 -		if ( --_nPendingScans <= 0 )
   3.308 -		{
   3.309 -			_nPendingScans = 0;
   3.310 -			[self.centralManager stopScan];
   3.311 -		}
   3.312 -	}
   3.313 -}
   3.314 -
   3.315 -
   3.316 -#pragma mark CBCentralManagerDelegate Implementation
   3.317 -
   3.318 -// called whenever the BLE hardware state changes.
   3.319 -- (void)centralManagerDidUpdateState:(CBCentralManager *)central
   3.320 -{
   3.321 -	switch ( central.state )
   3.322 -	{
   3.323 -		case CBCentralManagerStatePoweredOn:
   3.324 -		{
   3.325 -			NSLog( @"CoreBluetooth BLE hardware is powered on and ready" );
   3.326 -			
   3.327 -			// at startup, if we have no already attached peripherals, do a 20s scan for new unpaired devices,
   3.328 -			// otherwise callers should occaisionally do additional scans. we don't want to continuously be
   3.329 -			// scanning because it drains battery, causes other nearby people to have a hard time pairing their
   3.330 -			// Steam Controllers, and may also trigger firmware weirdness when a device attempts to start
   3.331 -			// the pairing sequence multiple times concurrently
   3.332 -			if ( [self updateConnectedSteamControllers:false] == 0 )
   3.333 -			{
   3.334 -				// TODO: we could limit our scan to only peripherals supporting the SteamController service, but
   3.335 -				//  that service doesn't currently fit in the base advertising packet, we'd need to put it into an
   3.336 -				//  extended scan packet. Useful optimization downstream, but not currently necessary
   3.337 -				//	NSArray *services = @[[CBUUID UUIDWithString:VALVE_SERVICE]];
   3.338 -				[self startScan:20];
   3.339 -			}
   3.340 -			break;
   3.341 -		}
   3.342 -			
   3.343 -		case CBCentralManagerStatePoweredOff:
   3.344 -			NSLog( @"CoreBluetooth BLE hardware is powered off" );
   3.345 -			break;
   3.346 -			
   3.347 -		case CBCentralManagerStateUnauthorized:
   3.348 -			NSLog( @"CoreBluetooth BLE state is unauthorized" );
   3.349 -			break;
   3.350 -			
   3.351 -		case CBCentralManagerStateUnknown:
   3.352 -			NSLog( @"CoreBluetooth BLE state is unknown" );
   3.353 -			break;
   3.354 -			
   3.355 -		case CBCentralManagerStateUnsupported:
   3.356 -			NSLog( @"CoreBluetooth BLE hardware is unsupported on this platform" );
   3.357 -			break;
   3.358 -		
   3.359 -		case CBCentralManagerStateResetting:
   3.360 -			NSLog( @"CoreBluetooth BLE manager is resetting" );
   3.361 -			break;
   3.362 -	}
   3.363 -}
   3.364 -
   3.365 -- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
   3.366 -{
   3.367 -	HIDBLEDevice *steamController = [_deviceMap objectForKey:peripheral];
   3.368 -	steamController.connected = YES;
   3.369 -	self.nPendingPairs -= 1;
   3.370 -}
   3.371 -
   3.372 -- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
   3.373 -{
   3.374 -	NSLog( @"Failed to connect: %@", error );
   3.375 -	[_deviceMap removeObjectForKey:peripheral];
   3.376 -	self.nPendingPairs -= 1;
   3.377 -}
   3.378 -
   3.379 -- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
   3.380 -{
   3.381 -	NSString *localName = [advertisementData objectForKey:CBAdvertisementDataLocalNameKey];
   3.382 -	NSString *log = [NSString stringWithFormat:@"Found '%@'", localName];
   3.383 -	
   3.384 -	if ( [localName isEqualToString:@"SteamController"] )
   3.385 -	{
   3.386 -		NSLog( @"%@ : %@ - %@", log, peripheral, advertisementData );
   3.387 -		self.nPendingPairs += 1;
   3.388 -		HIDBLEDevice *steamController = [[HIDBLEDevice alloc] initWithPeripheral:peripheral];
   3.389 -		[self.deviceMap setObject:steamController forKey:peripheral];
   3.390 -		[self.centralManager connectPeripheral:peripheral options:nil];
   3.391 -	}
   3.392 -}
   3.393 -
   3.394 -- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
   3.395 -{
   3.396 -	HIDBLEDevice *steamController = [self.deviceMap objectForKey:peripheral];
   3.397 -	if ( steamController )
   3.398 -	{
   3.399 -		steamController.connected = NO;
   3.400 -		steamController.ready = NO;
   3.401 -		[self.deviceMap removeObjectForKey:peripheral];
   3.402 -	}
   3.403 -}
   3.404 -
   3.405 -@end
   3.406 -
   3.407 -
   3.408 -// Core Bluetooth devices calling back on event boundaries of their run-loops. so annoying.
   3.409 -static void process_pending_events()
   3.410 -{
   3.411 -	CFRunLoopRunResult res;
   3.412 -	do
   3.413 -	{
   3.414 -		res = CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0.001, FALSE );
   3.415 -	}
   3.416 -	while( res != kCFRunLoopRunFinished && res != kCFRunLoopRunTimedOut );
   3.417 -}
   3.418 -
   3.419 -@implementation HIDBLEDevice
   3.420 -
   3.421 -- (id)init
   3.422 -{
   3.423 -	if ( self = [super init] )
   3.424 -	{
   3.425 -		self.bleSteamController = nil;
   3.426 -		self.bleCharacteristicInput = nil;
   3.427 -		self.bleCharacteristicReport = nil;
   3.428 -		_connected = NO;
   3.429 -		_ready = NO;
   3.430 -	}
   3.431 -	return self;
   3.432 -}
   3.433 -
   3.434 -- (id)initWithPeripheral:(CBPeripheral *)peripheral
   3.435 -{
   3.436 -	if ( self = [super init] )
   3.437 -	{
   3.438 -		_connected = NO;
   3.439 -		_ready = NO;
   3.440 -		self.bleSteamController = peripheral;
   3.441 -		if ( peripheral )
   3.442 -		{
   3.443 -			peripheral.delegate = self;
   3.444 -		}
   3.445 -		self.bleCharacteristicInput = nil;
   3.446 -		self.bleCharacteristicReport = nil;
   3.447 -	}
   3.448 -	return self;
   3.449 -}
   3.450 -
   3.451 -- (void)setConnected:(bool)connected
   3.452 -{
   3.453 -	_connected = connected;
   3.454 -	if ( _connected )
   3.455 -	{
   3.456 -		[_bleSteamController discoverServices:nil];
   3.457 -	}
   3.458 -	else
   3.459 -	{
   3.460 -		NSLog( @"Disconnected" );
   3.461 -	}
   3.462 -}
   3.463 -
   3.464 -- (size_t)read_input_report:(uint8_t *)dst
   3.465 -{
   3.466 -	if ( _inputReports.read( dst+1 ) )
   3.467 -	{
   3.468 -		*dst = 0x03;
   3.469 -		return 20;
   3.470 -	}
   3.471 -	return 0;
   3.472 -}
   3.473 -
   3.474 -- (int)send_report:(const uint8_t *)data length:(size_t)length
   3.475 -{
   3.476 -	[_bleSteamController writeValue:[NSData dataWithBytes:data length:length] forCharacteristic:_bleCharacteristicReport type:CBCharacteristicWriteWithResponse];
   3.477 -	return (int)length;
   3.478 -}
   3.479 -
   3.480 -- (int)send_feature_report:(hidFeatureReport *)report
   3.481 -{
   3.482 -#if FEATURE_REPORT_LOGGING
   3.483 -	uint8_t *reportBytes = (uint8_t *)report;
   3.484 -	
   3.485 -	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]", report->segment.size(),
   3.486 -		  reportBytes[1], reportBytes[2], reportBytes[3], reportBytes[4], reportBytes[5], reportBytes[6],
   3.487 -		  reportBytes[7], reportBytes[8], reportBytes[9], reportBytes[10], reportBytes[11], reportBytes[12],
   3.488 -		  reportBytes[13], reportBytes[14], reportBytes[15], reportBytes[16], reportBytes[17], reportBytes[18],
   3.489 -		  reportBytes[19] );
   3.490 -#endif
   3.491 -
   3.492 -	int sendSize = (int)report->segment.size();
   3.493 -	if ( sendSize > 20 )
   3.494 -		sendSize = 20;
   3.495 -
   3.496 -#if 1
   3.497 -	// fire-and-forget - we are going to not wait for the response here because all Steam Controller BLE send_feature_report's are ignored,
   3.498 -	//  except errors.
   3.499 -	[_bleSteamController writeValue:[NSData dataWithBytes:&report->segment length:sendSize] forCharacteristic:_bleCharacteristicReport type:CBCharacteristicWriteWithResponse];
   3.500 -	
   3.501 -	// pretend we received a result anybody cares about
   3.502 -	return 19;
   3.503 -
   3.504 -#else
   3.505 -	// this is technically the correct send_feature_report logic if you want to make sure it gets through and is
   3.506 -	// acknowledged or errors out
   3.507 -	_waitStateForWriteFeatureReport = BLEDeviceWaitState::Waiting;
   3.508 -	[_bleSteamController writeValue:[NSData dataWithBytes:&report->segment length:sendSize
   3.509 -									 ] forCharacteristic:_bleCharacteristicReport type:CBCharacteristicWriteWithResponse];
   3.510 -	
   3.511 -	while ( _waitStateForWriteFeatureReport == BLEDeviceWaitState::Waiting )
   3.512 -	{
   3.513 -		process_pending_events();
   3.514 -	}
   3.515 -	
   3.516 -	if ( _waitStateForWriteFeatureReport == BLEDeviceWaitState::Error )
   3.517 -	{
   3.518 -		_waitStateForWriteFeatureReport = BLEDeviceWaitState::None;
   3.519 -		return -1;
   3.520 -	}
   3.521 -	
   3.522 -	_waitStateForWriteFeatureReport = BLEDeviceWaitState::None;
   3.523 -	return 19;
   3.524 -#endif
   3.525 -}
   3.526 -
   3.527 -- (int)get_feature_report:(uint8_t)feature into:(uint8_t *)buffer
   3.528 -{
   3.529 -	_waitStateForReadFeatureReport = BLEDeviceWaitState::Waiting;
   3.530 -	[_bleSteamController readValueForCharacteristic:_bleCharacteristicReport];
   3.531 -	
   3.532 -	while ( _waitStateForReadFeatureReport == BLEDeviceWaitState::Waiting )
   3.533 -		process_pending_events();
   3.534 -	
   3.535 -	if ( _waitStateForReadFeatureReport == BLEDeviceWaitState::Error )
   3.536 -	{
   3.537 -		_waitStateForReadFeatureReport = BLEDeviceWaitState::None;
   3.538 -		return -1;
   3.539 -	}
   3.540 -	
   3.541 -	memcpy( buffer, _featureReport, sizeof(_featureReport) );
   3.542 -	
   3.543 -	_waitStateForReadFeatureReport = BLEDeviceWaitState::None;
   3.544 -	
   3.545 -#if FEATURE_REPORT_LOGGING
   3.546 -	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]",
   3.547 -		  buffer[1], buffer[2], buffer[3], buffer[4], buffer[5], buffer[6],
   3.548 -		  buffer[7], buffer[8], buffer[9], buffer[10], buffer[11], buffer[12],
   3.549 -		  buffer[13], buffer[14], buffer[15], buffer[16], buffer[17], buffer[18],
   3.550 -		  buffer[19] );
   3.551 -#endif
   3.552 -
   3.553 -	return 19;
   3.554 -}
   3.555 -
   3.556 -#pragma mark CBPeripheralDelegate Implementation
   3.557 -
   3.558 -- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error
   3.559 -{
   3.560 -	for (CBService *service in peripheral.services)
   3.561 -	{
   3.562 -		NSLog( @"Found Service: %@", service );
   3.563 -		if ( [service.UUID isEqual:[CBUUID UUIDWithString:VALVE_SERVICE]] )
   3.564 -		{
   3.565 -			[peripheral discoverCharacteristics:nil forService:service];
   3.566 -		}
   3.567 -	}
   3.568 -}
   3.569 -
   3.570 -- (void)peripheral:(CBPeripheral *)peripheral didDiscoverDescriptorsForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
   3.571 -{
   3.572 -	// nothing yet needed here, enable for logging
   3.573 -	if ( /* DISABLES CODE */ (0) )
   3.574 -	{
   3.575 -		for ( CBDescriptor *descriptor in characteristic.descriptors )
   3.576 -		{
   3.577 -			NSLog( @" - Descriptor '%@'", descriptor );
   3.578 -		}
   3.579 -	}
   3.580 -}
   3.581 -
   3.582 -- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
   3.583 -{
   3.584 -	if ([service.UUID isEqual:[CBUUID UUIDWithString:VALVE_SERVICE]])
   3.585 -	{
   3.586 -		for (CBCharacteristic *aChar in service.characteristics)
   3.587 -		{
   3.588 -			NSLog( @"Found Characteristic %@", aChar );
   3.589 -			
   3.590 -			if ( [aChar.UUID isEqual:[CBUUID UUIDWithString:VALVE_INPUT_CHAR]] )
   3.591 -			{
   3.592 -				self.bleCharacteristicInput = aChar;
   3.593 -			}
   3.594 -			else if ( [aChar.UUID isEqual:[CBUUID UUIDWithString:VALVE_REPORT_CHAR]] )
   3.595 -			{
   3.596 -				self.bleCharacteristicReport = aChar;
   3.597 -				[self.bleSteamController discoverDescriptorsForCharacteristic: aChar];
   3.598 -			}
   3.599 -		}
   3.600 -	}
   3.601 -}
   3.602 -
   3.603 -- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
   3.604 -{
   3.605 -	static uint64_t s_ticksLastOverflowReport = 0;
   3.606 -
   3.607 -	// receiving an input report is the final indicator that the user accepted a pairing
   3.608 -	// request and that we successfully established notification. CoreBluetooth has no
   3.609 -	// notification of the pairing acknowledgement, which is a bad oversight.
   3.610 -	if ( self.ready == NO )
   3.611 -	{
   3.612 -		self.ready = YES;
   3.613 -		HIDBLEManager.sharedInstance.nPendingPairs -= 1;
   3.614 -	}
   3.615 -
   3.616 -	if ( [characteristic.UUID isEqual:_bleCharacteristicInput.UUID] )
   3.617 -	{
   3.618 -		NSData *data = [characteristic value];
   3.619 -		if ( data.length != 19 )
   3.620 -		{
   3.621 -			NSLog( @"HIDBLE: incoming data is %lu bytes should be exactly 19", (unsigned long)data.length );
   3.622 -		}
   3.623 -		if ( !_inputReports.write( (const uint8_t *)data.bytes ) )
   3.624 -		{
   3.625 -			uint64_t ticksNow = mach_approximate_time();
   3.626 -			if ( ticksNow - s_ticksLastOverflowReport > (5ull * NSEC_PER_SEC / 10) )
   3.627 -			{
   3.628 -				NSLog( @"HIDBLE: input report buffer overflow" );
   3.629 -				s_ticksLastOverflowReport = ticksNow;
   3.630 -			}
   3.631 -		}
   3.632 -	}
   3.633 -	else if ( [characteristic.UUID isEqual:_bleCharacteristicReport.UUID] )
   3.634 -	{
   3.635 -		memset( _featureReport, 0, sizeof(_featureReport) );
   3.636 -		
   3.637 -		if ( error != nil )
   3.638 -		{
   3.639 -			NSLog( @"HIDBLE: get_feature_report error: %@", error );
   3.640 -			_waitStateForReadFeatureReport = BLEDeviceWaitState::Error;
   3.641 -		}
   3.642 -		else
   3.643 -		{
   3.644 -			NSData *data = [characteristic value];
   3.645 -			if ( data.length != 20 )
   3.646 -			{
   3.647 -				NSLog( @"HIDBLE: incoming data is %lu bytes should be exactly 20", (unsigned long)data.length );
   3.648 -			}
   3.649 -			memcpy( _featureReport, data.bytes, MIN( data.length, sizeof(_featureReport) ) );
   3.650 -			_waitStateForReadFeatureReport = BLEDeviceWaitState::Complete;
   3.651 -		}
   3.652 -	}
   3.653 -}
   3.654 -
   3.655 -- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
   3.656 -{
   3.657 -	if ( [characteristic.UUID isEqual:[CBUUID UUIDWithString:VALVE_REPORT_CHAR]] )
   3.658 -	{
   3.659 -		if ( error != nil )
   3.660 -		{
   3.661 -			NSLog( @"HIDBLE: write_feature_report error: %@", error );
   3.662 -			_waitStateForWriteFeatureReport = BLEDeviceWaitState::Error;
   3.663 -		}
   3.664 -		else
   3.665 -		{
   3.666 -			_waitStateForWriteFeatureReport = BLEDeviceWaitState::Complete;
   3.667 -		}
   3.668 -	}
   3.669 -}
   3.670 -
   3.671 -- (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
   3.672 -{
   3.673 -	NSLog( @"didUpdateNotifcationStateForCharacteristic %@ (%@)", characteristic, error );
   3.674 -}
   3.675 -
   3.676 -@end
   3.677 -
   3.678 -
   3.679 -#pragma mark hid_api implementation
   3.680 -
   3.681 -struct hid_device_ {
   3.682 -	HIDBLEDevice *device_handle;
   3.683 -	int blocking;
   3.684 -	hid_device *next;
   3.685 -};
   3.686 -
   3.687 -int HID_API_EXPORT HID_API_CALL hid_init(void)
   3.688 -{
   3.689 -	return ( HIDBLEManager.sharedInstance == nil ) ? -1 : 0;
   3.690 -}
   3.691 -
   3.692 -int HID_API_EXPORT HID_API_CALL hid_exit(void)
   3.693 -{
   3.694 -	return 0;
   3.695 -}
   3.696 -
   3.697 -void HID_API_EXPORT HID_API_CALL hid_ble_scan( bool bStart )
   3.698 -{
   3.699 -	HIDBLEManager *bleManager = HIDBLEManager.sharedInstance;
   3.700 -	if ( bStart )
   3.701 -	{
   3.702 -		[bleManager startScan:0];
   3.703 -	}
   3.704 -	else
   3.705 -	{
   3.706 -		[bleManager stopScan];
   3.707 -	}
   3.708 -}
   3.709 -
   3.710 -hid_device * HID_API_EXPORT hid_open_path( const char *path, int bExclusive /* = false */ )
   3.711 -{
   3.712 -	hid_device *result = NULL;
   3.713 -	NSString *nssPath = [NSString stringWithUTF8String:path];
   3.714 -	HIDBLEManager *bleManager = HIDBLEManager.sharedInstance;
   3.715 -	NSEnumerator<HIDBLEDevice *> *devices = [bleManager.deviceMap objectEnumerator];
   3.716 -	
   3.717 -	for ( HIDBLEDevice *device in devices )
   3.718 -	{
   3.719 -		// we have the device but it hasn't found its service or characteristics until it is connected
   3.720 -		if ( !device.ready || !device.connected || !device.bleCharacteristicInput )
   3.721 -			continue;
   3.722 -		
   3.723 -		if ( [device.bleSteamController.identifier.UUIDString isEqualToString:nssPath] )
   3.724 -		{
   3.725 -			result = (hid_device *)malloc( sizeof( hid_device ) );
   3.726 -			memset( result, 0, sizeof( hid_device ) );
   3.727 -			result->device_handle = device;
   3.728 -			result->blocking = NO;
   3.729 -			// enable reporting input events on the characteristic
   3.730 -			[device.bleSteamController setNotifyValue:YES forCharacteristic:device.bleCharacteristicInput];
   3.731 -			return result;
   3.732 -		}
   3.733 -	}
   3.734 -	return result;
   3.735 -}
   3.736 -
   3.737 -void  HID_API_EXPORT hid_free_enumeration(struct hid_device_info *devs)
   3.738 -{
   3.739 -	/* This function is identical to the Linux version. Platform independent. */
   3.740 -	struct hid_device_info *d = devs;
   3.741 -	while (d) {
   3.742 -		struct hid_device_info *next = d->next;
   3.743 -		free(d->path);
   3.744 -		free(d->serial_number);
   3.745 -		free(d->manufacturer_string);
   3.746 -		free(d->product_string);
   3.747 -		free(d);
   3.748 -		d = next;
   3.749 -	}
   3.750 -}
   3.751 -
   3.752 -int HID_API_EXPORT hid_set_nonblocking(hid_device *dev, int nonblock)
   3.753 -{
   3.754 -	/* All Nonblocking operation is handled by the library. */
   3.755 -	dev->blocking = !nonblock;
   3.756 -	
   3.757 -	return 0;
   3.758 -}
   3.759 -
   3.760 -struct hid_device_info  HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, unsigned short product_id)
   3.761 -{ @autoreleasepool {
   3.762 -	struct hid_device_info *root = NULL;
   3.763 -	
   3.764 -	if ( ( vendor_id == 0 && product_id == 0 ) ||
   3.765 -		 ( vendor_id == VALVE_USB_VID && product_id == D0G_BLE2_PID ) )
   3.766 -	{
   3.767 -		HIDBLEManager *bleManager = HIDBLEManager.sharedInstance;
   3.768 -		[bleManager updateConnectedSteamControllers:false];
   3.769 -		NSEnumerator<HIDBLEDevice *> *devices = [bleManager.deviceMap objectEnumerator];
   3.770 -		for ( HIDBLEDevice *device in devices )
   3.771 -		{
   3.772 -			// there are several brief windows in connecting to an already paired device and
   3.773 -			// one long window waiting for users to confirm pairing where we don't want
   3.774 -			// to consider a device ready - if we hand it back to SDL or another
   3.775 -			// Steam Controller consumer, their additional SC setup work will fail
   3.776 -			// in unusual/silent ways and we can actually corrupt the BLE stack for
   3.777 -			// the entire system and kill the appletv remote's Menu button (!)
   3.778 -			if ( device.bleSteamController.state != CBPeripheralStateConnected ||
   3.779 -				 device.connected == NO || device.ready == NO )
   3.780 -			{
   3.781 -				if ( device.ready == NO && device.bleCharacteristicInput != nil )
   3.782 -				{
   3.783 -					// attempt to register for input reports. this call will silently fail
   3.784 -					// until the pairing finalizes with user acceptance. oh, apple.
   3.785 -					[device.bleSteamController setNotifyValue:YES forCharacteristic:device.bleCharacteristicInput];
   3.786 -				}
   3.787 -				continue;
   3.788 -			}
   3.789 -			hid_device_info *device_info = (hid_device_info *)malloc( sizeof(hid_device_info) );
   3.790 -			memset( device_info, 0, sizeof(hid_device_info) );
   3.791 -			device_info->next = root;
   3.792 -			root = device_info;
   3.793 -			device_info->path = strdup( device.bleSteamController.identifier.UUIDString.UTF8String );
   3.794 -			device_info->vendor_id = VALVE_USB_VID;
   3.795 -			device_info->product_id = D0G_BLE2_PID;
   3.796 -			device_info->product_string = wcsdup( L"Steam Controller" );
   3.797 -			device_info->manufacturer_string = wcsdup( L"Valve Corporation" );
   3.798 -		}
   3.799 -	}
   3.800 -	return root;
   3.801 -}}
   3.802 -
   3.803 -int HID_API_EXPORT_CALL hid_get_manufacturer_string(hid_device *dev, wchar_t *string, size_t maxlen)
   3.804 -{
   3.805 -	static wchar_t s_wszManufacturer[] = L"Valve Corporation";
   3.806 -	wcsncpy( string, s_wszManufacturer, sizeof(s_wszManufacturer)/sizeof(s_wszManufacturer[0]) );
   3.807 -	return 0;
   3.808 -}
   3.809 -
   3.810 -int HID_API_EXPORT_CALL hid_get_product_string(hid_device *dev, wchar_t *string, size_t maxlen)
   3.811 -{
   3.812 -	static wchar_t s_wszProduct[] = L"Steam Controller";
   3.813 -	wcsncpy( string, s_wszProduct, sizeof(s_wszProduct)/sizeof(s_wszProduct[0]) );
   3.814 -	return 0;
   3.815 -}
   3.816 -
   3.817 -int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen)
   3.818 -{
   3.819 -	static wchar_t s_wszSerial[] = L"12345";
   3.820 -	wcsncpy( string, s_wszSerial, sizeof(s_wszSerial)/sizeof(s_wszSerial[0]) );
   3.821 -	return 0;
   3.822 -}
   3.823 -
   3.824 -int HID_API_EXPORT hid_write(hid_device *dev, const unsigned char *data, size_t length)
   3.825 -{
   3.826 -	if ( !dev->device_handle.connected )
   3.827 -		return -1;
   3.828 -
   3.829 -	return [dev->device_handle send_report:data length:length];
   3.830 -}
   3.831 -
   3.832 -void HID_API_EXPORT hid_close(hid_device *dev)
   3.833 -{
   3.834 -	// disable reporting input events on the characteristic
   3.835 -	if ( dev->device_handle.connected ) {
   3.836 -		[dev->device_handle.bleSteamController setNotifyValue:NO forCharacteristic:dev->device_handle.bleCharacteristicInput];
   3.837 -	}
   3.838 -
   3.839 -	free( dev );
   3.840 -}
   3.841 -
   3.842 -int HID_API_EXPORT hid_send_feature_report(hid_device *dev, const unsigned char *data, size_t length)
   3.843 -{
   3.844 -	if ( !dev->device_handle.connected )
   3.845 -		return -1;
   3.846 -
   3.847 -	return [dev->device_handle send_feature_report:(hidFeatureReport *)(void *)data];
   3.848 -}
   3.849 -
   3.850 -int HID_API_EXPORT hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length)
   3.851 -{
   3.852 -	if ( !dev->device_handle.connected )
   3.853 -		return -1;
   3.854 -
   3.855 -	size_t written = [dev->device_handle get_feature_report:data[0] into:data];
   3.856 -	
   3.857 -	return written == length-1 ? (int)length : (int)written;
   3.858 -}
   3.859 -
   3.860 -int HID_API_EXPORT hid_read(hid_device *dev, unsigned char *data, size_t length)
   3.861 -{
   3.862 -	if ( !dev->device_handle.connected )
   3.863 -		return -1;
   3.864 -
   3.865 -	return hid_read_timeout(dev, data, length, 0);
   3.866 -}
   3.867 -
   3.868 -int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t length, int milliseconds)
   3.869 -{
   3.870 -	if ( !dev->device_handle.connected )
   3.871 -		return -1;
   3.872 -	
   3.873 -	if ( milliseconds != 0 )
   3.874 -	{
   3.875 -		NSLog( @"hid_read_timeout with non-zero wait" );
   3.876 -	}
   3.877 -	int result = (int)[dev->device_handle read_input_report:data];
   3.878 -#if FEATURE_REPORT_LOGGING
   3.879 -	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,
   3.880 -		  data[1], data[2], data[3], data[4], data[5], data[6],
   3.881 -		  data[7], data[8], data[9], data[10], data[11], data[12],
   3.882 -		  data[13], data[14], data[15], data[16], data[17], data[18],
   3.883 -		  data[19] );
   3.884 -#endif
   3.885 -	return result;
   3.886 -}