#include "SDL_internal.h"
#if defined(SDL_PLATFORM_IOS) || defined(SDL_PLATFORM_TVOS)
#ifndef SDL_HIDAPI_DISABLED
#include "../SDL_hidapi_c.h"
#define hid_close PLATFORM_hid_close
#define hid_device PLATFORM_hid_device
#define hid_device_ PLATFORM_hid_device_
#define hid_enumerate PLATFORM_hid_enumerate
#define hid_error PLATFORM_hid_error
#define hid_exit PLATFORM_hid_exit
#define hid_free_enumeration PLATFORM_hid_free_enumeration
#define hid_get_device_info PLATFORM_hid_get_device_info
#define hid_get_feature_report PLATFORM_hid_get_feature_report
#define hid_get_indexed_string PLATFORM_hid_get_indexed_string
#define hid_get_input_report PLATFORM_hid_get_input_report
#define hid_get_manufacturer_string PLATFORM_hid_get_manufacturer_string
#define hid_get_product_string PLATFORM_hid_get_product_string
#define hid_get_report_descriptor PLATFORM_hid_get_report_descriptor
#define hid_get_serial_number_string PLATFORM_hid_get_serial_number_string
#define hid_init PLATFORM_hid_init
#define hid_open_path PLATFORM_hid_open_path
#define hid_open PLATFORM_hid_open
#define hid_read PLATFORM_hid_read
#define hid_read_timeout PLATFORM_hid_read_timeout
#define hid_send_feature_report PLATFORM_hid_send_feature_report
#define hid_set_nonblocking PLATFORM_hid_set_nonblocking
#define hid_version PLATFORM_hid_version
#define hid_version_str PLATFORM_hid_version_str
#define hid_write PLATFORM_hid_write
#include <CoreBluetooth/CoreBluetooth.h>
#include <QuartzCore/QuartzCore.h>
#import <UIKit/UIKit.h>
#import <mach/mach_time.h>
#include <pthread.h>
#include <sys/time.h>
#include <unistd.h>
#include "../hidapi/hidapi.h"
#define VALVE_USB_VID 0x28DE
#define D0G_BLE2_PID 0x1106
#define TRITON_BLE_PID 0x1303
typedef uint32_t uint32;
typedef uint64_t uint64;
#define FEATURE_REPORT_LOGGING 0
#define REPORT_SEGMENT_DATA_FLAG 0x80
#define REPORT_SEGMENT_LAST_FLAG 0x40
#define VALVE_SERVICE @"100F6C32-1735-4313-B402-38567131E5F3"
#define VALVE_INPUT_CHAR_0x1106 @"100F6C33-1735-4313-B402-38567131E5F3"
#define VALVE_INPUT_CHAR_0x1303 @"100F6C7A-1735-4313-B402-38567131E5F3"
#define VALVE_REPORT_CHAR @"100F6C34-1735-4313-B402-38567131E5F3"
#pragma pack(push,1)
typedef struct
{
uint8_t segmentHeader;
uint8_t featureReportMessageID;
uint8_t length;
uint8_t settingIdentifier;
union {
uint16_t usPayload;
uint32_t uPayload;
uint64_t ulPayload;
uint8_t ucPayload[15];
};
} bluetoothSegment;
typedef struct {
uint8_t id;
bluetoothSegment segment;
} hidFeatureReport;
#pragma pack(pop)
size_t GetBluetoothSegmentSize(bluetoothSegment *segment)
{
return segment->length + 3;
}
#define RingBuffer_nElem 256
typedef struct {
int _first, _last;
int _cbElem;
uint8_t *_data;
pthread_mutex_t accessLock;
} RingBuffer;
static RingBuffer *RingBuffer_alloc( int cbElem )
{
RingBuffer *this = (RingBuffer *)malloc( sizeof(*this) );
if (!this)
{
return NULL;
}
this->_first = -1;
this->_last = 0;
this->_cbElem = cbElem;
this->_data = (uint8_t *)malloc(RingBuffer_nElem * cbElem);
if ( !this->_data )
{
free( this );
return NULL;
}
pthread_mutex_init( &this->accessLock, 0 );
return this;
}
static void RingBuffer_free( RingBuffer *this )
{
if ( this )
{
free( this->_data );
free( this );
}
}
static bool RingBuffer_write( RingBuffer *this, const uint8_t *src )
{
if ( !this )
{
return false;
}
pthread_mutex_lock( &this->accessLock );
memcpy( &this->_data[ this->_last ], src, this->_cbElem );
if ( this->_first == -1 )
{
this->_first = this->_last;
}
this->_last = ( this->_last + this->_cbElem ) % (RingBuffer_nElem * this->_cbElem);
if ( this->_last == this->_first )
{
this->_first = ( this->_first + this->_cbElem ) % (RingBuffer_nElem * this->_cbElem);
pthread_mutex_unlock( &this->accessLock );
return false;
}
pthread_mutex_unlock( &this->accessLock );
return true;
}
static bool RingBuffer_read( RingBuffer *this, uint8_t *dst )
{
if ( !this )
{
return false;
}
pthread_mutex_lock( &this->accessLock );
if ( this->_first == -1 )
{
pthread_mutex_unlock( &this->accessLock );
return false;
}
memcpy( dst, &this->_data[ this->_first ], this->_cbElem );
this->_first = ( this->_first + this->_cbElem ) % (RingBuffer_nElem * this->_cbElem);
if ( this->_first == this->_last )
{
this->_first = -1;
}
pthread_mutex_unlock( &this->accessLock );
return true;
}
#pragma mark HIDBLEDevice Definition
typedef enum
{
BLEDeviceWaitState_None,
BLEDeviceWaitState_Waiting,
BLEDeviceWaitState_Complete,
BLEDeviceWaitState_Error
} BLEDeviceWaitState;
@interface HIDBLEDevice : NSObject <CBPeripheralDelegate>
{
RingBuffer *_inputReports;
NSData *_featureReport;
NSMutableDictionary *_outputReports;
BLEDeviceWaitState _waitStateForReadFeatureReport;
BLEDeviceWaitState _waitStateForWriteFeatureReport;
}
@property (nonatomic, readwrite) uint16_t pid;
@property (nonatomic, readwrite) bool connected;
@property (nonatomic, readwrite) bool ready;
@property (nonatomic, strong) CBPeripheral *bleSteamController;
@property (nonatomic, strong) CBCharacteristic *bleCharacteristicInput;
@property (nonatomic, strong) CBCharacteristic *bleCharacteristicReport;
- (id)initWithPeripheral:(CBPeripheral *)peripheral;
- (void)onDisconnect;
@end
@interface HIDBLEManager : NSObject <CBCentralManagerDelegate>
@property (nonatomic) int nPendingScans;
@property (nonatomic) int nPendingPairs;
@property (nonatomic, strong) CBCentralManager *centralManager;
@property (nonatomic, strong) NSMapTable<CBPeripheral *, HIDBLEDevice *> *deviceMap;
@property (nonatomic, retain) dispatch_queue_t bleSerialQueue;
+ (instancetype)sharedInstance;
- (void)startScan:(int)duration;
- (void)stopScan;
- (int)updateConnectedSteamControllers:(BOOL) bForce;
- (void)appWillResignActiveNotification:(NSNotification *)note;
- (void)appDidBecomeActiveNotification:(NSNotification *)note;
@end
@implementation HIDBLEManager
+ (instancetype)sharedInstance
{
static HIDBLEManager *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [HIDBLEManager new];
sharedInstance.nPendingScans = 0;
sharedInstance.nPendingPairs = 0;
if ( SDL_GetHintBoolean( SDL_HINT_JOYSTICK_HIDAPI_STEAM, false ) )
{
[[NSNotificationCenter defaultCenter] addObserver:sharedInstance selector:@selector(appWillResignActiveNotification:) name: UIApplicationWillResignActiveNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:sharedInstance selector:@selector(appDidBecomeActiveNotification:) name:UIApplicationDidBecomeActiveNotification object:nil];
sharedInstance.bleSerialQueue = dispatch_queue_create( "com.valvesoftware.steamcontroller.ble", DISPATCH_QUEUE_SERIAL );
dispatch_set_target_queue( sharedInstance.bleSerialQueue, dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_HIGH, 0 ) );
sharedInstance.centralManager = [[CBCentralManager alloc] initWithDelegate:sharedInstance queue:sharedInstance.bleSerialQueue];
}
sharedInstance.deviceMap = [[NSMapTable alloc] initWithKeyOptions:NSMapTableWeakMemory valueOptions:NSMapTableStrongMemory capacity:4];
});
return sharedInstance;
}
- (void)appWillResignActiveNotification:(NSNotification *)note
{
if ( self.nPendingPairs > 0 )
return;
for ( CBPeripheral *peripheral in self.deviceMap )
{
HIDBLEDevice *steamController = [self.deviceMap objectForKey:peripheral];
if ( steamController )
{
[steamController onDisconnect];
[self.centralManager cancelPeripheralConnection:peripheral];
}
}
[self.deviceMap removeAllObjects];
}
- (void)appDidBecomeActiveNotification:(NSNotification *)note
{
[self updateConnectedSteamControllers:true];
[self startScan:20];
}
- (int)updateConnectedSteamControllers:(BOOL) bForce
{
static uint64_t s_unLastUpdateTick = 0;
static mach_timebase_info_data_t s_timebase_info;
if ( self.centralManager == nil )
{
return 0;
}
if (s_timebase_info.denom == 0)
{
mach_timebase_info( &s_timebase_info );
}
uint64_t ticksNow = mach_approximate_time();
if ( !bForce && ( ( (ticksNow - s_unLastUpdateTick) * s_timebase_info.numer ) / s_timebase_info.denom ) < (5ull * NSEC_PER_SEC) )
return (int)self.deviceMap.count;
if ( self.centralManager.state != CBManagerStatePoweredOn )
return (int)self.deviceMap.count;
s_unLastUpdateTick = mach_approximate_time();
if ( self.nPendingPairs > 0 )
return (int)self.deviceMap.count;
NSArray<CBPeripheral *> *peripherals = [self.centralManager retrieveConnectedPeripheralsWithServices: @[ [CBUUID UUIDWithString:@"180A"]]];
for ( CBPeripheral *peripheral in peripherals )
{
if ( [self.deviceMap objectForKey: peripheral] != nil )
continue;
NSLog( @"connected peripheral: %@", peripheral );
if ( [peripheral.name hasPrefix:@"Steam"] )
{
self.nPendingPairs += 1;
HIDBLEDevice *steamController = [[HIDBLEDevice alloc] initWithPeripheral:peripheral];
[self.deviceMap setObject:steamController forKey:peripheral];
[self.centralManager connectPeripheral:peripheral options:nil];
}
}
return (int)self.deviceMap.count;
}
- (void)startScan:(int)duration
{
if ( self.centralManager == nil )
{
return;
}
NSLog( @"BLE: requesting scan for %d seconds", duration );
@synchronized (self)
{
if ( _nPendingScans++ == 0 )
{
[self.centralManager scanForPeripheralsWithServices:nil options:nil];
}
}
if ( duration != 0 )
{
dispatch_after( dispatch_time( DISPATCH_TIME_NOW, (int64_t)(duration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self stopScan];
});
}
}
- (void)stopScan
{
if ( self.centralManager == nil )
{
return;
}
NSLog( @"BLE: stopping scan" );
@synchronized (self)
{
if ( --_nPendingScans <= 0 )
{
_nPendingScans = 0;
[self.centralManager stopScan];
}
}
}
#pragma mark CBCentralManagerDelegate Implementation
- (void)centralManagerDidUpdateState:(CBCentralManager *)central
{
switch ( central.state )
{
case CBManagerStatePoweredOn:
{
NSLog( @"CoreBluetooth BLE hardware is powered on and ready" );
if ( [self updateConnectedSteamControllers:false] == 0 )
{
[self startScan:20];
}
break;
}
case CBManagerStatePoweredOff:
NSLog( @"CoreBluetooth BLE hardware is powered off" );
break;
case CBManagerStateUnauthorized:
NSLog( @"CoreBluetooth BLE state is unauthorized" );
break;
case CBManagerStateUnknown:
NSLog( @"CoreBluetooth BLE state is unknown" );
break;
case CBManagerStateUnsupported:
NSLog( @"CoreBluetooth BLE hardware is unsupported on this platform" );
break;
case CBManagerStateResetting:
NSLog( @"CoreBluetooth BLE manager is resetting" );
break;
}
}
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
{
HIDBLEDevice *steamController = [_deviceMap objectForKey:peripheral];
steamController.connected = YES;
self.nPendingPairs -= 1;
}
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
{
NSLog( @"Failed to connect: %@", error );
[_deviceMap removeObjectForKey:peripheral];
self.nPendingPairs -= 1;
}
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
{
NSString *localName = [advertisementData objectForKey:CBAdvertisementDataLocalNameKey];
NSString *log = [NSString stringWithFormat:@"Found '%@'", localName];
if ( [localName hasPrefix:@"Steam"] )
{
NSLog( @"%@ : %@ - %@", log, peripheral, advertisementData );
self.nPendingPairs += 1;
HIDBLEDevice *steamController = [[HIDBLEDevice alloc] initWithPeripheral:peripheral];
[self.deviceMap setObject:steamController forKey:peripheral];
[self.centralManager connectPeripheral:peripheral options:nil];
}
}
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
{
HIDBLEDevice *steamController = [self.deviceMap objectForKey:peripheral];
if ( steamController )
{
[steamController onDisconnect];
[self.deviceMap removeObjectForKey:peripheral];
}
}
@end
static void process_pending_events(void)
{
CFRunLoopRunResult res;
do
{
res = CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0.001, FALSE );
}
while( res != kCFRunLoopRunFinished && res != kCFRunLoopRunTimedOut );
}
@implementation HIDBLEDevice
- (id)init
{
if ( self = [super init] )
{
self.pid = 0;
_inputReports = NULL;
_outputReports = [[NSMutableDictionary alloc] init];
_connected = NO;
_ready = NO;
self.bleSteamController = nil;
self.bleCharacteristicInput = nil;
self.bleCharacteristicReport = nil;
}
return self;
}
- (id)initWithPeripheral:(CBPeripheral *)peripheral
{
if ( self = [super init] )
{
self.pid = 0;
_inputReports = NULL;
_outputReports = [[NSMutableDictionary alloc] init];
_connected = NO;
_ready = NO;
self.bleSteamController = peripheral;
if ( peripheral )
{
peripheral.delegate = self;
}
self.bleCharacteristicInput = nil;
self.bleCharacteristicReport = nil;
}
return self;
}
- (void)onDisconnect
{
self.connected = NO;
self.ready = NO;
if ( _inputReports )
{
RingBuffer_free( _inputReports );
_inputReports = NULL;
}
}
- (void)setConnected:(bool)connected
{
_connected = connected;
if ( _connected )
{
[_bleSteamController discoverServices:nil];
}
else
{
NSLog( @"Disconnected" );
}
}
- (size_t)read_input_report:(uint8_t *)dst
{
if ( RingBuffer_read( _inputReports, dst+1 ) )
{
switch ( self.pid )
{
case D0G_BLE2_PID:
*dst = 0x03;
break;
case TRITON_BLE_PID:
*dst = 0x42;
break;
default:
abort();
}
return _inputReports->_cbElem + 1;
}
return 0;
}
- (int)send_report:(const uint8_t *)data length:(size_t)length
{
if ( self.pid == D0G_BLE2_PID )
{
[_bleSteamController writeValue:[NSData dataWithBytes:data length:length] forCharacteristic:_bleCharacteristicReport type:CBCharacteristicWriteWithResponse];
return (int)length;
}
if ( length > 0 )
{
CBCharacteristic *aChar = [_outputReports objectForKey:[NSNumber numberWithInt:data[0]]];
if ( aChar != nil )
{
[_bleSteamController writeValue:[NSData dataWithBytes:&data[1] length:(length - 1)] forCharacteristic:aChar type:CBCharacteristicWriteWithResponse];
return (int)length;
}
}
return -1;
}
- (int)send_feature_report:(hidFeatureReport *)report length:(size_t)length
{
#if FEATURE_REPORT_LOGGING
uint8_t *reportBytes = (uint8_t *)report;
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]", length,
reportBytes[1], reportBytes[2], reportBytes[3], reportBytes[4], reportBytes[5], reportBytes[6],
reportBytes[7], reportBytes[8], reportBytes[9], reportBytes[10], reportBytes[11], reportBytes[12],
reportBytes[13], reportBytes[14], reportBytes[15], reportBytes[16], reportBytes[17], reportBytes[18],
reportBytes[19] );
#endif
#if 1
[_bleSteamController writeValue:[NSData dataWithBytes:&report->segment length:MIN(length, 64)] forCharacteristic:_bleCharacteristicReport type:CBCharacteristicWriteWithResponse];
return (int)length;
#else#endif
}
- (int)get_feature_report:(uint8_t)feature into:(uint8_t *)buffer length:(size_t)length
{
_waitStateForReadFeatureReport = BLEDeviceWaitState_Waiting;
[_bleSteamController readValueForCharacteristic:_bleCharacteristicReport];
while ( _connected && _waitStateForReadFeatureReport == BLEDeviceWaitState_Waiting )
{
process_pending_events();
}
if ( !_connected || _waitStateForReadFeatureReport == BLEDeviceWaitState_Error )
{
_waitStateForReadFeatureReport = BLEDeviceWaitState_None;
return -1;
}
int amount = 0;
if ( _featureReport.length > 0 )
{
uint8_t *data = (uint8_t *)_featureReport.bytes;
if ( *data == *buffer )
{
amount = (int)MIN( length, _featureReport.length );
memcpy( buffer, _featureReport.bytes, amount );
}
else
{
amount = (int)MIN( length - 1, _featureReport.length );
memcpy( &buffer[ 1 ], _featureReport.bytes, amount );
++amount;
}
}
_waitStateForReadFeatureReport = BLEDeviceWaitState_None;
#if FEATURE_REPORT_LOGGING
NSLog( @"HIDBLE:get_feature_report (%lu/%zu) [%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x]",
_featureReport.length, length,
buffer[1], buffer[2], buffer[3], buffer[4], buffer[5], buffer[6],
buffer[7], buffer[8], buffer[9], buffer[10], buffer[11], buffer[12],
buffer[13], buffer[14], buffer[15], buffer[16], buffer[17], buffer[18],
buffer[19] );
#endif
return amount;
}
#pragma mark CBPeripheralDelegate Implementation
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error
{
for (CBService *service in peripheral.services)
{
NSLog( @"Found Service: %@", service );
if ( [service.UUID isEqual:[CBUUID UUIDWithString:VALVE_SERVICE]] )
{
[peripheral discoverCharacteristics:nil forService:service];
}
}
}
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverDescriptorsForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
{
if ( (0) )
{
for ( CBDescriptor *descriptor in characteristic.descriptors )
{
NSLog( @" - Descriptor '%@'", descriptor );
}
}
}
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
{
if ([service.UUID isEqual:[CBUUID UUIDWithString:VALVE_SERVICE]])
{
for (CBCharacteristic *aChar in service.characteristics)
{
NSLog( @"Found Characteristic %@", aChar );
if ( [aChar.UUID isEqual:[CBUUID UUIDWithString:VALVE_INPUT_CHAR_0x1106]] )
{
self.pid = D0G_BLE2_PID;
self.bleCharacteristicInput = aChar;
}
else if ( [aChar.UUID isEqual:[CBUUID UUIDWithString:VALVE_INPUT_CHAR_0x1303]] )
{
self.pid = TRITON_BLE_PID;
self.bleCharacteristicInput = aChar;
}
else if ( [aChar.UUID isEqual:[CBUUID UUIDWithString:VALVE_REPORT_CHAR]] )
{
self.bleCharacteristicReport = aChar;
[self.bleSteamController discoverDescriptorsForCharacteristic: aChar];
}
else
{
NSString *UUIDString = [aChar.UUID UUIDString];
int report_id = 0;
if ( sscanf( UUIDString.UTF8String, "100F6C%x", &report_id ) == 1 && report_id > 0x35 )
{
report_id -= 0x35;
if (report_id >= 0x80) {
[_outputReports setObject:aChar forKey:[NSNumber numberWithInt:report_id]];
}
}
}
}
}
}
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
{
static uint64_t s_ticksLastOverflowReport = 0;
if ( self.ready == NO )
{
self.ready = YES;
if ( _inputReports == NULL )
{
int cbElem = 0;
switch ( self.pid )
{
case D0G_BLE2_PID:
cbElem = 19;
break;
case TRITON_BLE_PID:
cbElem = 53;
break;
default:
abort();
}
_inputReports = RingBuffer_alloc( cbElem );
}
HIDBLEManager.sharedInstance.nPendingPairs -= 1;
}
if ( [characteristic.UUID isEqual:_bleCharacteristicInput.UUID] )
{
NSData *data = [characteristic value];
if ( _inputReports && data.length != _inputReports->_cbElem )
{
NSLog( @"HIDBLE: incoming data is %lu bytes should be exactly %d", (unsigned long)data.length, _inputReports->_cbElem );
}
if ( !RingBuffer_write( _inputReports, (const uint8_t *)data.bytes ) )
{
uint64_t ticksNow = mach_approximate_time();
if ( ticksNow - s_ticksLastOverflowReport > (5ull * NSEC_PER_SEC / 10) )
{
NSLog( @"HIDBLE: input report buffer overflow" );
s_ticksLastOverflowReport = ticksNow;
}
}
}
else if ( [characteristic.UUID isEqual:_bleCharacteristicReport.UUID] )
{
if ( error != nil )
{
NSLog( @"HIDBLE: get_feature_report error: %@", error );
_waitStateForReadFeatureReport = BLEDeviceWaitState_Error;
}
else
{
_featureReport = [characteristic value];
_waitStateForReadFeatureReport = BLEDeviceWaitState_Complete;
}
}
}
- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
{
if ( [characteristic.UUID isEqual:[CBUUID UUIDWithString:VALVE_REPORT_CHAR]] )
{
if ( error != nil )
{
NSLog( @"HIDBLE: write_feature_report error: %@", error );
_waitStateForWriteFeatureReport = BLEDeviceWaitState_Error;
}
else
{
_waitStateForWriteFeatureReport = BLEDeviceWaitState_Complete;
}
}
}
- (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
{
NSLog( @"didUpdateNotificationStateForCharacteristic %@ (%@)", characteristic, error );
}
@end
#pragma mark hid_api implementation
struct hid_device_ {
void *device_handle;
int blocking;
struct hid_device_info* device_info;
hid_device *next;
};
int HID_API_EXPORT HID_API_CALL hid_init(void)
{
return ( HIDBLEManager.sharedInstance == nil ) ? -1 : 0;
}
int HID_API_EXPORT HID_API_CALL hid_exit(void)
{
return 0;
}
void HID_API_EXPORT HID_API_CALL hid_ble_scan( int bStart )
{
HIDBLEManager *bleManager = HIDBLEManager.sharedInstance;
if ( bStart )
{
[bleManager startScan:0];
}
else
{
[bleManager stopScan];
}
}
HID_API_EXPORT hid_device * HID_API_CALL hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number)
{
return NULL;
}
HID_API_EXPORT hid_device * HID_API_CALL hid_open_path( const char *path )
{
hid_device *result = NULL;
NSString *nssPath = [NSString stringWithUTF8String:path];
HIDBLEManager *bleManager = HIDBLEManager.sharedInstance;
NSEnumerator<HIDBLEDevice *> *devices = [bleManager.deviceMap objectEnumerator];
for ( HIDBLEDevice *device in devices )
{
if ( !device.ready || !device.connected || !device.bleCharacteristicInput )
continue;
if ( [device.bleSteamController.identifier.UUIDString isEqualToString:nssPath] )
{
result = (hid_device *)malloc( sizeof( hid_device ) );
memset( result, 0, sizeof( hid_device ) );
result->device_handle = (void*)CFBridgingRetain( device );
result->blocking = NO;
[device.bleSteamController setNotifyValue:YES forCharacteristic:device.bleCharacteristicInput];
return result;
}
}
return result;
}
void HID_API_EXPORT hid_free_enumeration(struct hid_device_info *devs)
{
struct hid_device_info *d = devs;
while (d) {
struct hid_device_info *next = d->next;
free(d->path);
free(d->serial_number);
free(d->manufacturer_string);
free(d->product_string);
free(d);
d = next;
}
}
int HID_API_EXPORT hid_set_nonblocking(hid_device *dev, int nonblock)
{
dev->blocking = !nonblock;
return 0;
}
static struct hid_device_info *create_device_info_for_hid_device(HIDBLEDevice *device)
{
struct hid_device_info *device_info = (struct hid_device_info *)malloc( sizeof(struct hid_device_info) );
memset( device_info, 0, sizeof(struct hid_device_info) );
device_info->path = strdup( device.bleSteamController.identifier.UUIDString.UTF8String );
device_info->vendor_id = VALVE_USB_VID;
device_info->product_id = device.pid;
device_info->product_string = wcsdup( L"Steam Controller" );
device_info->manufacturer_string = wcsdup( L"Valve Corporation" );
device_info->bus_type = HID_API_BUS_BLUETOOTH;
return device_info;
}
struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, unsigned short product_id)
{ @autoreleasepool {
struct hid_device_info *root = NULL;
HIDBLEManager *bleManager = HIDBLEManager.sharedInstance;
[bleManager updateConnectedSteamControllers:false];
NSEnumerator<HIDBLEDevice *> *devices = [bleManager.deviceMap objectEnumerator];
for ( HIDBLEDevice *device in devices )
{
if ( device.bleSteamController.state != CBPeripheralStateConnected ||
device.connected == NO || device.ready == NO )
{
if ( device.ready == NO && device.bleCharacteristicInput != nil )
{
[device.bleSteamController setNotifyValue:YES forCharacteristic:device.bleCharacteristicInput];
}
continue;
}
if ( ( vendor_id != 0 && vendor_id != VALVE_USB_VID ) ||
( product_id != 0 && product_id != device.pid ) )
{
continue;
}
if (SDL_HIDAPI_ShouldIgnoreDevice(HID_API_BUS_BLUETOOTH, VALVE_USB_VID, device.pid, 0, 0, false)) {
continue;
}
struct hid_device_info *device_info = create_device_info_for_hid_device(device);
device_info->next = root;
root = device_info;
}
return root;
}}
int HID_API_EXPORT_CALL hid_get_manufacturer_string(hid_device *dev, wchar_t *string, size_t maxlen)
{
static wchar_t s_wszManufacturer[] = L"Valve Corporation";
wcsncpy( string, s_wszManufacturer, sizeof(s_wszManufacturer)/sizeof(s_wszManufacturer[0]) );
return 0;
}
int HID_API_EXPORT_CALL hid_get_product_string(hid_device *dev, wchar_t *string, size_t maxlen)
{
static wchar_t s_wszProduct[] = L"Steam Controller";
wcsncpy( string, s_wszProduct, sizeof(s_wszProduct)/sizeof(s_wszProduct[0]) );
return 0;
}
int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen)
{
static wchar_t s_wszSerial[] = L"12345";
wcsncpy( string, s_wszSerial, sizeof(s_wszSerial)/sizeof(s_wszSerial[0]) );
return 0;
}
int HID_API_EXPORT_CALL hid_get_indexed_string(hid_device *dev, int string_index, wchar_t *string, size_t maxlen)
{
return -1;
}
struct hid_device_info *hid_get_device_info(hid_device *dev)
{
HIDBLEDevice *device_handle = (__bridge HIDBLEDevice *)dev->device_handle;
if (!dev->device_info) {
dev->device_info = create_device_info_for_hid_device(device_handle);
}
return dev->device_info;
}
int hid_get_report_descriptor(hid_device *device, unsigned char *buf, size_t buf_size)
{
return -1;
}
int HID_API_EXPORT hid_write(hid_device *dev, const unsigned char *data, size_t length)
{
HIDBLEDevice *device_handle = (__bridge HIDBLEDevice *)dev->device_handle;
if ( !device_handle.connected )
return -1;
return [device_handle send_report:data length:length];
}
void HID_API_EXPORT hid_close(hid_device *dev)
{
HIDBLEDevice *device_handle = CFBridgingRelease( dev->device_handle );
if ( device_handle.connected ) {
[device_handle.bleSteamController setNotifyValue:NO forCharacteristic:device_handle.bleCharacteristicInput];
}
hid_free_enumeration(dev->device_info);
free( dev );
}
int HID_API_EXPORT hid_send_feature_report(hid_device *dev, const unsigned char *data, size_t length)
{
HIDBLEDevice *device_handle = (__bridge HIDBLEDevice *)dev->device_handle;
if ( !device_handle.connected )
return -1;
return [device_handle send_feature_report:(hidFeatureReport *)(void *)data length:length];
}
int HID_API_EXPORT hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length)
{
HIDBLEDevice *device_handle = (__bridge HIDBLEDevice *)dev->device_handle;
if ( !device_handle.connected )
return -1;
size_t written = [device_handle get_feature_report:data[0] into:data length:length];
return written == length-1 ? (int)length : (int)written;
}
int HID_API_EXPORT hid_get_input_report(hid_device *dev, unsigned char *data, size_t length)
{
return -1;
}
int HID_API_EXPORT hid_read(hid_device *dev, unsigned char *data, size_t length)
{
HIDBLEDevice *device_handle = (__bridge HIDBLEDevice *)dev->device_handle;
if ( !device_handle.connected )
return -1;
return hid_read_timeout(dev, data, length, 0);
}
int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t length, int milliseconds)
{
HIDBLEDevice *device_handle = (__bridge HIDBLEDevice *)dev->device_handle;
if ( !device_handle.connected )
return -1;
if ( milliseconds != 0 )
{
NSLog( @"hid_read_timeout with non-zero wait" );
}
int result = (int)[device_handle read_input_report:data];
#if 0#endif
return result;
}
HID_API_EXPORT const wchar_t* HID_API_CALL hid_error(hid_device *dev)
{
return NULL;
}
#endif
#endif