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