| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915 |
- //======== Copyright (c) 2017 Valve Corporation, All rights reserved. =========
- //
- // Purpose: HID device abstraction temporary stub
- //
- //=============================================================================
- #include "../../SDL_internal.h"
- #ifdef SDL_JOYSTICK_HIDAPI
- #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
- typedef uint32_t uint32;
- typedef uint64_t uint64;
- // enables detailed NSLog logging of feature reports
- #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"
- // (READ/NOTIFICATIONS)
- #define VALVE_INPUT_CHAR @"100F6C33-1735-4313-B402-38567131E5F3"
- // (READ/WRITE)
- #define VALVE_REPORT_CHAR @"100F6C34-1735-4313-B402-38567131E5F3"
- // TODO: create CBUUID's in __attribute__((constructor)) rather than doing [CBUUID UUIDWithString:...] everywhere
- #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;
- union {
- bluetoothSegment segment;
- 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];
- };
- };
- };
- } hidFeatureReport;
- #pragma pack(pop)
- size_t GetBluetoothSegmentSize(bluetoothSegment *segment)
- {
- return segment->length + 3;
- }
- #define RingBuffer_cbElem 19
- #define RingBuffer_nElem 4096
- typedef struct {
- int _first, _last;
- uint8_t _data[ ( RingBuffer_nElem * RingBuffer_cbElem ) ];
- pthread_mutex_t accessLock;
- } RingBuffer;
- static void RingBuffer_init( RingBuffer *this )
- {
- this->_first = -1;
- this->_last = 0;
- pthread_mutex_init( &this->accessLock, 0 );
- }
-
- static bool RingBuffer_write( RingBuffer *this, const uint8_t *src )
- {
- pthread_mutex_lock( &this->accessLock );
- memcpy( &this->_data[ this->_last ], src, RingBuffer_cbElem );
- if ( this->_first == -1 )
- {
- this->_first = this->_last;
- }
- this->_last = ( this->_last + RingBuffer_cbElem ) % (RingBuffer_nElem * RingBuffer_cbElem);
- if ( this->_last == this->_first )
- {
- this->_first = ( this->_first + RingBuffer_cbElem ) % (RingBuffer_nElem * RingBuffer_cbElem);
- pthread_mutex_unlock( &this->accessLock );
- return false;
- }
- pthread_mutex_unlock( &this->accessLock );
- return true;
- }
- static bool RingBuffer_read( RingBuffer *this, uint8_t *dst )
- {
- pthread_mutex_lock( &this->accessLock );
- if ( this->_first == -1 )
- {
- pthread_mutex_unlock( &this->accessLock );
- return false;
- }
- memcpy( dst, &this->_data[ this->_first ], RingBuffer_cbElem );
- this->_first = ( this->_first + RingBuffer_cbElem ) % (RingBuffer_nElem * RingBuffer_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;
- uint8_t _featureReport[20];
- BLEDeviceWaitState _waitStateForReadFeatureReport;
- BLEDeviceWaitState _waitStateForWriteFeatureReport;
- }
- @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;
- @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
- // singleton class - access using HIDBLEManager.sharedInstance
- @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;
-
- [[NSNotificationCenter defaultCenter] addObserver:sharedInstance selector:@selector(appWillResignActiveNotification:) name: UIApplicationWillResignActiveNotification object:nil];
- [[NSNotificationCenter defaultCenter] addObserver:sharedInstance selector:@selector(appDidBecomeActiveNotification:) name:UIApplicationDidBecomeActiveNotification object:nil];
- // receive reports on a high-priority serial-queue. optionally put writes on the serial queue to avoid logical
- // race conditions talking to the controller from multiple threads, although BLE fragmentation/assembly means
- // that we can still screw this up.
- // most importantly we need to consume reports at a high priority to avoid the OS thinking we aren't really
- // listening to the BLE device, as iOS on slower devices may stop delivery of packets to the app WITHOUT ACTUALLY
- // DISCONNECTING FROM THE DEVICE if we don't react quickly enough to their delivery.
- // see also the error-handling states in the peripheral delegate to re-open the device if it gets closed
- 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 ) );
- // creating a CBCentralManager will always trigger a future centralManagerDidUpdateState:
- // where any scanning gets started or connecting to existing peripherals happens, it's never already in a
- // powered-on state for a newly launched application.
- sharedInstance.centralManager = [[CBCentralManager alloc] initWithDelegate:sharedInstance queue:sharedInstance.bleSerialQueue];
- sharedInstance.deviceMap = [[NSMapTable alloc] initWithKeyOptions:NSMapTableWeakMemory valueOptions:NSMapTableStrongMemory capacity:4];
- });
- return sharedInstance;
- }
- // called for NSNotification UIApplicationWillResignActiveNotification
- - (void)appWillResignActiveNotification:(NSNotification *)note
- {
- // we'll get resign-active notification if pairing is happening.
- if ( self.nPendingPairs > 0 )
- return;
- for ( CBPeripheral *peripheral in self.deviceMap )
- {
- HIDBLEDevice *steamController = [self.deviceMap objectForKey:peripheral];
- if ( steamController )
- {
- steamController.connected = NO;
- steamController.ready = NO;
- [self.centralManager cancelPeripheralConnection:peripheral];
- }
- }
- [self.deviceMap removeAllObjects];
- }
- // called for NSNotification UIApplicationDidBecomeActiveNotification
- // whenever the application comes back from being inactive, trigger a 20s pairing scan and reconnect
- // any devices that may have paired while we were inactive.
- - (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 (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;
-
- // we can see previously connected BLE peripherals but can't connect until the CBCentralManager
- // is fully powered up - only do work when we are in that state
- if ( self.centralManager.state != CBManagerStatePoweredOn )
- return (int)self.deviceMap.count;
- // only update our last-check-time if we actually did work, otherwise there can be a long delay during initial power-up
- s_unLastUpdateTick = mach_approximate_time();
-
- // if a pair is in-flight, the central manager may still give it back via retrieveConnected... and
- // cause the SDL layer to attempt to initialize it while some of its endpoints haven't yet been established
- if ( self.nPendingPairs > 0 )
- return (int)self.deviceMap.count;
- NSArray<CBPeripheral *> *peripherals = [self.centralManager retrieveConnectedPeripheralsWithServices: @[ [CBUUID UUIDWithString:@"180A"]]];
- for ( CBPeripheral *peripheral in peripherals )
- {
- // we already know this peripheral
- if ( [self.deviceMap objectForKey: peripheral] != nil )
- continue;
-
- NSLog( @"connected peripheral: %@", peripheral );
- if ( [peripheral.name isEqualToString:@"SteamController"] )
- {
- 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;
- }
- // manual API for folks to start & stop scanning
- - (void)startScan:(int)duration
- {
- 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
- {
- NSLog( @"BLE: stopping scan" );
- @synchronized (self)
- {
- if ( --_nPendingScans <= 0 )
- {
- _nPendingScans = 0;
- [self.centralManager stopScan];
- }
- }
- }
- #pragma mark CBCentralManagerDelegate Implementation
- // called whenever the BLE hardware state changes.
- - (void)centralManagerDidUpdateState:(CBCentralManager *)central
- {
- switch ( central.state )
- {
- case CBCentralManagerStatePoweredOn:
- {
- NSLog( @"CoreBluetooth BLE hardware is powered on and ready" );
-
- // at startup, if we have no already attached peripherals, do a 20s scan for new unpaired devices,
- // otherwise callers should occaisionally do additional scans. we don't want to continuously be
- // scanning because it drains battery, causes other nearby people to have a hard time pairing their
- // Steam Controllers, and may also trigger firmware weirdness when a device attempts to start
- // the pairing sequence multiple times concurrently
- if ( [self updateConnectedSteamControllers:false] == 0 )
- {
- // TODO: we could limit our scan to only peripherals supporting the SteamController service, but
- // that service doesn't currently fit in the base advertising packet, we'd need to put it into an
- // extended scan packet. Useful optimization downstream, but not currently necessary
- // NSArray *services = @[[CBUUID UUIDWithString:VALVE_SERVICE]];
- [self startScan:20];
- }
- break;
- }
-
- case CBCentralManagerStatePoweredOff:
- NSLog( @"CoreBluetooth BLE hardware is powered off" );
- break;
-
- case CBCentralManagerStateUnauthorized:
- NSLog( @"CoreBluetooth BLE state is unauthorized" );
- break;
-
- case CBCentralManagerStateUnknown:
- NSLog( @"CoreBluetooth BLE state is unknown" );
- break;
-
- case CBCentralManagerStateUnsupported:
- NSLog( @"CoreBluetooth BLE hardware is unsupported on this platform" );
- break;
-
- case CBCentralManagerStateResetting:
- 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 isEqualToString:@"SteamController"] )
- {
- 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.connected = NO;
- steamController.ready = NO;
- [self.deviceMap removeObjectForKey:peripheral];
- }
- }
- @end
- // Core Bluetooth devices calling back on event boundaries of their run-loops. so annoying.
- static void process_pending_events()
- {
- CFRunLoopRunResult res;
- do
- {
- res = CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0.001, FALSE );
- }
- while( res != kCFRunLoopRunFinished && res != kCFRunLoopRunTimedOut );
- }
- @implementation HIDBLEDevice
- - (id)init
- {
- if ( self = [super init] )
- {
- RingBuffer_init( &_inputReports );
- self.bleSteamController = nil;
- self.bleCharacteristicInput = nil;
- self.bleCharacteristicReport = nil;
- _connected = NO;
- _ready = NO;
- }
- return self;
- }
- - (id)initWithPeripheral:(CBPeripheral *)peripheral
- {
- if ( self = [super init] )
- {
- RingBuffer_init( &_inputReports );
- _connected = NO;
- _ready = NO;
- self.bleSteamController = peripheral;
- if ( peripheral )
- {
- peripheral.delegate = self;
- }
- self.bleCharacteristicInput = nil;
- self.bleCharacteristicReport = nil;
- }
- return self;
- }
- - (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 ) )
- {
- *dst = 0x03;
- return 20;
- }
- return 0;
- }
- - (int)send_report:(const uint8_t *)data length:(size_t)length
- {
- [_bleSteamController writeValue:[NSData dataWithBytes:data length:length] forCharacteristic:_bleCharacteristicReport type:CBCharacteristicWriteWithResponse];
- return (int)length;
- }
- - (int)send_feature_report:(hidFeatureReport *)report
- {
- #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]", GetBluetoothSegmentSize( report->segment ),
- 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
- int sendSize = (int)GetBluetoothSegmentSize( &report->segment );
- if ( sendSize > 20 )
- sendSize = 20;
- #if 1
- // fire-and-forget - we are going to not wait for the response here because all Steam Controller BLE send_feature_report's are ignored,
- // except errors.
- [_bleSteamController writeValue:[NSData dataWithBytes:&report->segment length:sendSize] forCharacteristic:_bleCharacteristicReport type:CBCharacteristicWriteWithResponse];
-
- // pretend we received a result anybody cares about
- return 19;
- #else
- // this is technically the correct send_feature_report logic if you want to make sure it gets through and is
- // acknowledged or errors out
- _waitStateForWriteFeatureReport = BLEDeviceWaitState_Waiting;
- [_bleSteamController writeValue:[NSData dataWithBytes:&report->segment length:sendSize
- ] forCharacteristic:_bleCharacteristicReport type:CBCharacteristicWriteWithResponse];
-
- while ( _waitStateForWriteFeatureReport == BLEDeviceWaitState_Waiting )
- {
- process_pending_events();
- }
-
- if ( _waitStateForWriteFeatureReport == BLEDeviceWaitState_Error )
- {
- _waitStateForWriteFeatureReport = BLEDeviceWaitState_None;
- return -1;
- }
-
- _waitStateForWriteFeatureReport = BLEDeviceWaitState_None;
- return 19;
- #endif
- }
- - (int)get_feature_report:(uint8_t)feature into:(uint8_t *)buffer
- {
- _waitStateForReadFeatureReport = BLEDeviceWaitState_Waiting;
- [_bleSteamController readValueForCharacteristic:_bleCharacteristicReport];
-
- while ( _waitStateForReadFeatureReport == BLEDeviceWaitState_Waiting )
- process_pending_events();
-
- if ( _waitStateForReadFeatureReport == BLEDeviceWaitState_Error )
- {
- _waitStateForReadFeatureReport = BLEDeviceWaitState_None;
- return -1;
- }
-
- memcpy( buffer, _featureReport, sizeof(_featureReport) );
-
- _waitStateForReadFeatureReport = BLEDeviceWaitState_None;
-
- #if FEATURE_REPORT_LOGGING
- 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]",
- 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 19;
- }
- #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
- {
- // nothing yet needed here, enable for logging
- if ( /* DISABLES CODE */ (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]] )
- {
- self.bleCharacteristicInput = aChar;
- }
- else if ( [aChar.UUID isEqual:[CBUUID UUIDWithString:VALVE_REPORT_CHAR]] )
- {
- self.bleCharacteristicReport = aChar;
- [self.bleSteamController discoverDescriptorsForCharacteristic: aChar];
- }
- }
- }
- }
- - (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
- {
- static uint64_t s_ticksLastOverflowReport = 0;
- // receiving an input report is the final indicator that the user accepted a pairing
- // request and that we successfully established notification. CoreBluetooth has no
- // notification of the pairing acknowledgement, which is a bad oversight.
- if ( self.ready == NO )
- {
- self.ready = YES;
- HIDBLEManager.sharedInstance.nPendingPairs -= 1;
- }
- if ( [characteristic.UUID isEqual:_bleCharacteristicInput.UUID] )
- {
- NSData *data = [characteristic value];
- if ( data.length != 19 )
- {
- NSLog( @"HIDBLE: incoming data is %lu bytes should be exactly 19", (unsigned long)data.length );
- }
- 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] )
- {
- memset( _featureReport, 0, sizeof(_featureReport) );
-
- if ( error != nil )
- {
- NSLog( @"HIDBLE: get_feature_report error: %@", error );
- _waitStateForReadFeatureReport = BLEDeviceWaitState_Error;
- }
- else
- {
- NSData *data = [characteristic value];
- if ( data.length != 20 )
- {
- NSLog( @"HIDBLE: incoming data is %lu bytes should be exactly 20", (unsigned long)data.length );
- }
- memcpy( _featureReport, data.bytes, MIN( data.length, sizeof(_featureReport) ) );
- _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( @"didUpdateNotifcationStateForCharacteristic %@ (%@)", characteristic, error );
- }
- @end
- #pragma mark hid_api implementation
- struct hid_device_ {
- void *device_handle;
- int blocking;
- 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( bool bStart )
- {
- HIDBLEManager *bleManager = HIDBLEManager.sharedInstance;
- if ( bStart )
- {
- [bleManager startScan:0];
- }
- else
- {
- [bleManager stopScan];
- }
- }
- hid_device * HID_API_EXPORT hid_open_path( const char *path, int bExclusive /* = false */ )
- {
- hid_device *result = NULL;
- NSString *nssPath = [NSString stringWithUTF8String:path];
- HIDBLEManager *bleManager = HIDBLEManager.sharedInstance;
- NSEnumerator<HIDBLEDevice *> *devices = [bleManager.deviceMap objectEnumerator];
-
- for ( HIDBLEDevice *device in devices )
- {
- // we have the device but it hasn't found its service or characteristics until it is connected
- 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;
- // enable reporting input events on the characteristic
- [device.bleSteamController setNotifyValue:YES forCharacteristic:device.bleCharacteristicInput];
- return result;
- }
- }
- return result;
- }
- void HID_API_EXPORT hid_free_enumeration(struct hid_device_info *devs)
- {
- /* This function is identical to the Linux version. Platform independent. */
- 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)
- {
- /* All Nonblocking operation is handled by the library. */
- dev->blocking = !nonblock;
-
- return 0;
- }
- struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, unsigned short product_id)
- { @autoreleasepool {
- struct hid_device_info *root = NULL;
-
- if ( ( vendor_id == 0 && product_id == 0 ) ||
- ( vendor_id == VALVE_USB_VID && product_id == D0G_BLE2_PID ) )
- {
- HIDBLEManager *bleManager = HIDBLEManager.sharedInstance;
- [bleManager updateConnectedSteamControllers:false];
- NSEnumerator<HIDBLEDevice *> *devices = [bleManager.deviceMap objectEnumerator];
- for ( HIDBLEDevice *device in devices )
- {
- // there are several brief windows in connecting to an already paired device and
- // one long window waiting for users to confirm pairing where we don't want
- // to consider a device ready - if we hand it back to SDL or another
- // Steam Controller consumer, their additional SC setup work will fail
- // in unusual/silent ways and we can actually corrupt the BLE stack for
- // the entire system and kill the appletv remote's Menu button (!)
- if ( device.bleSteamController.state != CBPeripheralStateConnected ||
- device.connected == NO || device.ready == NO )
- {
- if ( device.ready == NO && device.bleCharacteristicInput != nil )
- {
- // attempt to register for input reports. this call will silently fail
- // until the pairing finalizes with user acceptance. oh, apple.
- [device.bleSteamController setNotifyValue:YES forCharacteristic:device.bleCharacteristicInput];
- }
- continue;
- }
- 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->next = root;
- root = device_info;
- device_info->path = strdup( device.bleSteamController.identifier.UUIDString.UTF8String );
- device_info->vendor_id = VALVE_USB_VID;
- device_info->product_id = D0G_BLE2_PID;
- device_info->product_string = wcsdup( L"Steam Controller" );
- device_info->manufacturer_string = wcsdup( L"Valve Corporation" );
- }
- }
- 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 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 );
- // disable reporting input events on the characteristic
- if ( device_handle.connected ) {
- [device_handle.bleSteamController setNotifyValue:NO forCharacteristic:device_handle.bleCharacteristicInput];
- }
- 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];
- }
- 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];
-
- return written == length-1 ? (int)length : (int)written;
- }
- 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 FEATURE_REPORT_LOGGING
- 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,
- data[1], data[2], data[3], data[4], data[5], data[6],
- data[7], data[8], data[9], data[10], data[11], data[12],
- data[13], data[14], data[15], data[16], data[17], data[18],
- data[19] );
- #endif
- return result;
- }
- #endif /* SDL_JOYSTICK_HIDAPI */
|