Atlas - hid.m
Home / ext / SDL / src / hidapi / ios Lines: 1 | Size: 36542 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)][FILE BEGIN]1/* 2 Simple DirectMedia Layer 3 Copyright (C) 2021 Valve Corporation 4 5 This software is provided 'as-is', without any express or implied 6 warranty. In no event will the authors be held liable for any damages 7 arising from the use of this software. 8 9 Permission is granted to anyone to use this software for any purpose, 10 including commercial applications, and to alter it and redistribute it 11 freely, subject to the following restrictions: 12 13 1. The origin of this software must not be misrepresented; you must not 14 claim that you wrote the original software. If you use this software 15 in a product, an acknowledgment in the product documentation would be 16 appreciated but is not required. 17 2. Altered source versions must be plainly marked as such, and must not be 18 misrepresented as being the original software. 19 3. This notice may not be removed or altered from any source distribution. 20*/ 21#include "SDL_internal.h" 22 23#if defined(SDL_PLATFORM_IOS) || defined(SDL_PLATFORM_TVOS) 24 25#ifndef SDL_HIDAPI_DISABLED 26 27#include "../SDL_hidapi_c.h" 28 29#define hid_close PLATFORM_hid_close 30#define hid_device PLATFORM_hid_device 31#define hid_device_ PLATFORM_hid_device_ 32#define hid_enumerate PLATFORM_hid_enumerate 33#define hid_error PLATFORM_hid_error 34#define hid_exit PLATFORM_hid_exit 35#define hid_free_enumeration PLATFORM_hid_free_enumeration 36#define hid_get_device_info PLATFORM_hid_get_device_info 37#define hid_get_feature_report PLATFORM_hid_get_feature_report 38#define hid_get_indexed_string PLATFORM_hid_get_indexed_string 39#define hid_get_input_report PLATFORM_hid_get_input_report 40#define hid_get_manufacturer_string PLATFORM_hid_get_manufacturer_string 41#define hid_get_product_string PLATFORM_hid_get_product_string 42#define hid_get_report_descriptor PLATFORM_hid_get_report_descriptor 43#define hid_get_serial_number_string PLATFORM_hid_get_serial_number_string 44#define hid_init PLATFORM_hid_init 45#define hid_open_path PLATFORM_hid_open_path 46#define hid_open PLATFORM_hid_open 47#define hid_read PLATFORM_hid_read 48#define hid_read_timeout PLATFORM_hid_read_timeout 49#define hid_send_feature_report PLATFORM_hid_send_feature_report 50#define hid_set_nonblocking PLATFORM_hid_set_nonblocking 51#define hid_version PLATFORM_hid_version 52#define hid_version_str PLATFORM_hid_version_str 53#define hid_write PLATFORM_hid_write 54 55#include <CoreBluetooth/CoreBluetooth.h> 56#include <QuartzCore/QuartzCore.h> 57#import <UIKit/UIKit.h> 58#import <mach/mach_time.h> 59#include <pthread.h> 60#include <sys/time.h> 61#include <unistd.h> 62#include "../hidapi/hidapi.h" 63 64#define VALVE_USB_VID 0x28DE 65#define D0G_BLE2_PID 0x1106 66#define TRITON_BLE_PID 0x1303 67 68typedef uint32_t uint32; 69typedef uint64_t uint64; 70 71// enables detailed NSLog logging of feature reports 72#define FEATURE_REPORT_LOGGING 0 73 74#define REPORT_SEGMENT_DATA_FLAG 0x80 75#define REPORT_SEGMENT_LAST_FLAG 0x40 76 77#define VALVE_SERVICE @"100F6C32-1735-4313-B402-38567131E5F3" 78 79// (READ/NOTIFICATIONS) 80#define VALVE_INPUT_CHAR_0x1106 @"100F6C33-1735-4313-B402-38567131E5F3" 81#define VALVE_INPUT_CHAR_0x1303 @"100F6C77-1735-4313-B402-38567131E5F3" 82 83// (READ/WRITE) 84#define VALVE_REPORT_CHAR @"100F6C34-1735-4313-B402-38567131E5F3" 85 86// TODO: create CBUUID's in __attribute__((constructor)) rather than doing [CBUUID UUIDWithString:...] everywhere 87 88#pragma pack(push,1) 89 90typedef struct 91{ 92 uint8_t segmentHeader; 93 uint8_t featureReportMessageID; 94 uint8_t length; 95 uint8_t settingIdentifier; 96 union { 97 uint16_t usPayload; 98 uint32_t uPayload; 99 uint64_t ulPayload; 100 uint8_t ucPayload[15]; 101 }; 102} bluetoothSegment; 103 104typedef struct { 105 uint8_t id; 106 bluetoothSegment segment; 107} hidFeatureReport; 108 109#pragma pack(pop) 110 111size_t GetBluetoothSegmentSize(bluetoothSegment *segment) 112{ 113 return segment->length + 3; 114} 115 116#define RingBuffer_nElem 256 117 118typedef struct { 119 int _first, _last; 120 int _cbElem; 121 uint8_t *_data; 122 pthread_mutex_t accessLock; 123} RingBuffer; 124 125static RingBuffer *RingBuffer_alloc( int cbElem ) 126{ 127 RingBuffer *this = (RingBuffer *)malloc( sizeof(*this) ); 128 if (!this) 129{ 130 return NULL; 131 } 132 133 this->_first = -1; 134 this->_last = 0; 135 this->_cbElem = cbElem; 136 this->_data = (uint8_t *)malloc(RingBuffer_nElem * cbElem); 137 if ( !this->_data ) 138 { 139 free( this ); 140 return NULL; 141 } 142 pthread_mutex_init( &this->accessLock, 0 ); 143 return this; 144} 145 146static void RingBuffer_free( RingBuffer *this ) 147{ 148 if ( this ) 149 { 150 free( this->_data ); 151 free( this ); 152 } 153} 154 155static bool RingBuffer_write( RingBuffer *this, const uint8_t *src ) 156{ 157 if ( !this ) 158 { 159 return false; 160 } 161 162 pthread_mutex_lock( &this->accessLock ); 163 memcpy( &this->_data[ this->_last ], src, this->_cbElem ); 164 if ( this->_first == -1 ) 165 { 166 this->_first = this->_last; 167 } 168 this->_last = ( this->_last + this->_cbElem ) % (RingBuffer_nElem * this->_cbElem); 169 if ( this->_last == this->_first ) 170 { 171 this->_first = ( this->_first + this->_cbElem ) % (RingBuffer_nElem * this->_cbElem); 172 pthread_mutex_unlock( &this->accessLock ); 173 return false; 174 } 175 pthread_mutex_unlock( &this->accessLock ); 176 return true; 177} 178 179static bool RingBuffer_read( RingBuffer *this, uint8_t *dst ) 180{ 181 if ( !this ) 182 { 183 return false; 184 } 185 186 pthread_mutex_lock( &this->accessLock ); 187 if ( this->_first == -1 ) 188 { 189 pthread_mutex_unlock( &this->accessLock ); 190 return false; 191 } 192 memcpy( dst, &this->_data[ this->_first ], this->_cbElem ); 193 this->_first = ( this->_first + this->_cbElem ) % (RingBuffer_nElem * this->_cbElem); 194 if ( this->_first == this->_last ) 195 { 196 this->_first = -1; 197 } 198 pthread_mutex_unlock( &this->accessLock ); 199 return true; 200} 201 202 203#pragma mark HIDBLEDevice Definition 204 205typedef enum 206{ 207 BLEDeviceWaitState_None, 208 BLEDeviceWaitState_Waiting, 209 BLEDeviceWaitState_Complete, 210 BLEDeviceWaitState_Error 211} BLEDeviceWaitState; 212 213@interface HIDBLEDevice : NSObject <CBPeripheralDelegate> 214{ 215 RingBuffer *_inputReports; 216 NSData *_featureReport; 217 NSMutableDictionary *_outputReports; 218 BLEDeviceWaitState _waitStateForReadFeatureReport; 219 BLEDeviceWaitState _waitStateForWriteFeatureReport; 220} 221 222@property (nonatomic, readwrite) uint16_t pid; 223@property (nonatomic, readwrite) bool connected; 224@property (nonatomic, readwrite) bool ready; 225 226@property (nonatomic, strong) CBPeripheral *bleSteamController; 227@property (nonatomic, strong) CBCharacteristic *bleCharacteristicInput; 228@property (nonatomic, strong) CBCharacteristic *bleCharacteristicReport; 229 230- (id)initWithPeripheral:(CBPeripheral *)peripheral; 231- (void)onDisconnect; 232 233@end 234 235 236@interface HIDBLEManager : NSObject <CBCentralManagerDelegate> 237 238@property (nonatomic) int nPendingScans; 239@property (nonatomic) int nPendingPairs; 240@property (nonatomic, strong) CBCentralManager *centralManager; 241@property (nonatomic, strong) NSMapTable<CBPeripheral *, HIDBLEDevice *> *deviceMap; 242@property (nonatomic, retain) dispatch_queue_t bleSerialQueue; 243 244+ (instancetype)sharedInstance; 245- (void)startScan:(int)duration; 246- (void)stopScan; 247- (int)updateConnectedSteamControllers:(BOOL) bForce; 248- (void)appWillResignActiveNotification:(NSNotification *)note; 249- (void)appDidBecomeActiveNotification:(NSNotification *)note; 250 251@end 252 253 254// singleton class - access using HIDBLEManager.sharedInstance 255@implementation HIDBLEManager 256 257+ (instancetype)sharedInstance 258{ 259 static HIDBLEManager *sharedInstance = nil; 260 static dispatch_once_t onceToken; 261 dispatch_once(&onceToken, ^{ 262 sharedInstance = [HIDBLEManager new]; 263 sharedInstance.nPendingScans = 0; 264 sharedInstance.nPendingPairs = 0; 265 266 // Bluetooth is currently only used for Steam Controllers, so check that hint 267 // before initializing Bluetooth, which will prompt the user for permission. 268 if ( SDL_GetHintBoolean( SDL_HINT_JOYSTICK_HIDAPI_STEAM, false ) ) 269 { 270 [[NSNotificationCenter defaultCenter] addObserver:sharedInstance selector:@selector(appWillResignActiveNotification:) name: UIApplicationWillResignActiveNotification object:nil]; 271 [[NSNotificationCenter defaultCenter] addObserver:sharedInstance selector:@selector(appDidBecomeActiveNotification:) name:UIApplicationDidBecomeActiveNotification object:nil]; 272 273 // receive reports on a high-priority serial-queue. optionally put writes on the serial queue to avoid logical 274 // race conditions talking to the controller from multiple threads, although BLE fragmentation/assembly means 275 // that we can still screw this up. 276 // most importantly we need to consume reports at a high priority to avoid the OS thinking we aren't really 277 // listening to the BLE device, as iOS on slower devices may stop delivery of packets to the app WITHOUT ACTUALLY 278 // DISCONNECTING FROM THE DEVICE if we don't react quickly enough to their delivery. 279 // see also the error-handling states in the peripheral delegate to re-open the device if it gets closed 280 sharedInstance.bleSerialQueue = dispatch_queue_create( "com.valvesoftware.steamcontroller.ble", DISPATCH_QUEUE_SERIAL ); 281 dispatch_set_target_queue( sharedInstance.bleSerialQueue, dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_HIGH, 0 ) ); 282 283 // creating a CBCentralManager will always trigger a future centralManagerDidUpdateState: 284 // where any scanning gets started or connecting to existing peripherals happens, it's never already in a 285 // powered-on state for a newly launched application. 286 sharedInstance.centralManager = [[CBCentralManager alloc] initWithDelegate:sharedInstance queue:sharedInstance.bleSerialQueue]; 287 } 288 sharedInstance.deviceMap = [[NSMapTable alloc] initWithKeyOptions:NSMapTableWeakMemory valueOptions:NSMapTableStrongMemory capacity:4]; 289 }); 290 return sharedInstance; 291} 292 293// called for NSNotification UIApplicationWillResignActiveNotification 294- (void)appWillResignActiveNotification:(NSNotification *)note 295{ 296 // we'll get resign-active notification if pairing is happening. 297 if ( self.nPendingPairs > 0 ) 298 return; 299 300 for ( CBPeripheral *peripheral in self.deviceMap ) 301 { 302 HIDBLEDevice *steamController = [self.deviceMap objectForKey:peripheral]; 303 if ( steamController ) 304 { 305 [steamController onDisconnect]; 306 [self.centralManager cancelPeripheralConnection:peripheral]; 307 } 308 } 309 [self.deviceMap removeAllObjects]; 310} 311 312// called for NSNotification UIApplicationDidBecomeActiveNotification 313// whenever the application comes back from being inactive, trigger a 20s pairing scan and reconnect 314// any devices that may have paired while we were inactive. 315- (void)appDidBecomeActiveNotification:(NSNotification *)note 316{ 317 [self updateConnectedSteamControllers:true]; 318 [self startScan:20]; 319} 320 321- (int)updateConnectedSteamControllers:(BOOL) bForce 322{ 323 static uint64_t s_unLastUpdateTick = 0; 324 static mach_timebase_info_data_t s_timebase_info; 325 326 if ( self.centralManager == nil ) 327 { 328 return 0; 329 } 330 331 if (s_timebase_info.denom == 0) 332 { 333 mach_timebase_info( &s_timebase_info ); 334 } 335 336 uint64_t ticksNow = mach_approximate_time(); 337 if ( !bForce && ( ( (ticksNow - s_unLastUpdateTick) * s_timebase_info.numer ) / s_timebase_info.denom ) < (5ull * NSEC_PER_SEC) ) 338 return (int)self.deviceMap.count; 339 340 // we can see previously connected BLE peripherals but can't connect until the CBCentralManager 341 // is fully powered up - only do work when we are in that state 342 if ( self.centralManager.state != CBManagerStatePoweredOn ) 343 return (int)self.deviceMap.count; 344 345 // only update our last-check-time if we actually did work, otherwise there can be a long delay during initial power-up 346 s_unLastUpdateTick = mach_approximate_time(); 347 348 // if a pair is in-flight, the central manager may still give it back via retrieveConnected... and 349 // cause the SDL layer to attempt to initialize it while some of its endpoints haven't yet been established 350 if ( self.nPendingPairs > 0 ) 351 return (int)self.deviceMap.count; 352 353 NSArray<CBPeripheral *> *peripherals = [self.centralManager retrieveConnectedPeripheralsWithServices: @[ [CBUUID UUIDWithString:@"180A"]]]; 354 for ( CBPeripheral *peripheral in peripherals ) 355 { 356 // we already know this peripheral 357 if ( [self.deviceMap objectForKey: peripheral] != nil ) 358 continue; 359 360 NSLog( @"connected peripheral: %@", peripheral ); 361 if ( [peripheral.name hasPrefix:@"Steam"] ) 362 { 363 self.nPendingPairs += 1; 364 HIDBLEDevice *steamController = [[HIDBLEDevice alloc] initWithPeripheral:peripheral]; 365 [self.deviceMap setObject:steamController forKey:peripheral]; 366 [self.centralManager connectPeripheral:peripheral options:nil]; 367 } 368 } 369 370 return (int)self.deviceMap.count; 371} 372 373// manual API for folks to start & stop scanning 374- (void)startScan:(int)duration 375{ 376 if ( self.centralManager == nil ) 377 { 378 return; 379 } 380 381 NSLog( @"BLE: requesting scan for %d seconds", duration ); 382 @synchronized (self) 383 { 384 if ( _nPendingScans++ == 0 ) 385 { 386 [self.centralManager scanForPeripheralsWithServices:nil options:nil]; 387 } 388 } 389 390 if ( duration != 0 ) 391 { 392 dispatch_after( dispatch_time( DISPATCH_TIME_NOW, (int64_t)(duration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 393 [self stopScan]; 394 }); 395 } 396} 397 398- (void)stopScan 399{ 400 if ( self.centralManager == nil ) 401 { 402 return; 403 } 404 405 NSLog( @"BLE: stopping scan" ); 406 @synchronized (self) 407 { 408 if ( --_nPendingScans <= 0 ) 409 { 410 _nPendingScans = 0; 411 [self.centralManager stopScan]; 412 } 413 } 414} 415 416 417#pragma mark CBCentralManagerDelegate Implementation 418 419// called whenever the BLE hardware state changes. 420- (void)centralManagerDidUpdateState:(CBCentralManager *)central 421{ 422 switch ( central.state ) 423 { 424 case CBManagerStatePoweredOn: 425 { 426 NSLog( @"CoreBluetooth BLE hardware is powered on and ready" ); 427 428 // at startup, if we have no already attached peripherals, do a 20s scan for new unpaired devices, 429 // otherwise callers should occasionally do additional scans. we don't want to continuously be 430 // scanning because it drains battery, causes other nearby people to have a hard time pairing their 431 // Steam Controllers, and may also trigger firmware weirdness when a device attempts to start 432 // the pairing sequence multiple times concurrently 433 if ( [self updateConnectedSteamControllers:false] == 0 ) 434 { 435 // TODO: we could limit our scan to only peripherals supporting the SteamController service, but 436 // that service doesn't currently fit in the base advertising packet, we'd need to put it into an 437 // extended scan packet. Useful optimization downstream, but not currently necessary 438 // NSArray *services = @[[CBUUID UUIDWithString:VALVE_SERVICE]]; 439 [self startScan:20]; 440 } 441 break; 442 } 443 444 case CBManagerStatePoweredOff: 445 NSLog( @"CoreBluetooth BLE hardware is powered off" ); 446 break; 447 448 case CBManagerStateUnauthorized: 449 NSLog( @"CoreBluetooth BLE state is unauthorized" ); 450 break; 451 452 case CBManagerStateUnknown: 453 NSLog( @"CoreBluetooth BLE state is unknown" ); 454 break; 455 456 case CBManagerStateUnsupported: 457 NSLog( @"CoreBluetooth BLE hardware is unsupported on this platform" ); 458 break; 459 460 case CBManagerStateResetting: 461 NSLog( @"CoreBluetooth BLE manager is resetting" ); 462 break; 463 } 464} 465 466- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral 467{ 468 HIDBLEDevice *steamController = [_deviceMap objectForKey:peripheral]; 469 steamController.connected = YES; 470 self.nPendingPairs -= 1; 471} 472 473- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error 474{ 475 NSLog( @"Failed to connect: %@", error ); 476 [_deviceMap removeObjectForKey:peripheral]; 477 self.nPendingPairs -= 1; 478} 479 480- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI 481{ 482 NSString *localName = [advertisementData objectForKey:CBAdvertisementDataLocalNameKey]; 483 NSString *log = [NSString stringWithFormat:@"Found '%@'", localName]; 484 485 if ( [localName hasPrefix:@"Steam"] ) 486 { 487 NSLog( @"%@ : %@ - %@", log, peripheral, advertisementData ); 488 self.nPendingPairs += 1; 489 HIDBLEDevice *steamController = [[HIDBLEDevice alloc] initWithPeripheral:peripheral]; 490 [self.deviceMap setObject:steamController forKey:peripheral]; 491 [self.centralManager connectPeripheral:peripheral options:nil]; 492 } 493} 494 495- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error 496{ 497 HIDBLEDevice *steamController = [self.deviceMap objectForKey:peripheral]; 498 if ( steamController ) 499 { 500 [steamController onDisconnect]; 501 [self.deviceMap removeObjectForKey:peripheral]; 502 } 503} 504 505@end 506 507 508// Core Bluetooth devices calling back on event boundaries of their run-loops. so annoying. 509static void process_pending_events(void) 510{ 511 CFRunLoopRunResult res; 512 do 513 { 514 res = CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0.001, FALSE ); 515 } 516 while( res != kCFRunLoopRunFinished && res != kCFRunLoopRunTimedOut ); 517} 518 519@implementation HIDBLEDevice 520 521- (id)init 522{ 523 if ( self = [super init] ) 524 { 525 self.pid = 0; 526 _inputReports = NULL; 527 _outputReports = [[NSMutableDictionary alloc] init]; 528 _connected = NO; 529 _ready = NO; 530 self.bleSteamController = nil; 531 self.bleCharacteristicInput = nil; 532 self.bleCharacteristicReport = nil; 533 } 534 return self; 535} 536 537- (id)initWithPeripheral:(CBPeripheral *)peripheral 538{ 539 if ( self = [super init] ) 540 { 541 self.pid = 0; 542 _inputReports = NULL; 543 _outputReports = [[NSMutableDictionary alloc] init]; 544 _connected = NO; 545 _ready = NO; 546 self.bleSteamController = peripheral; 547 if ( peripheral ) 548 { 549 peripheral.delegate = self; 550 } 551 self.bleCharacteristicInput = nil; 552 self.bleCharacteristicReport = nil; 553 } 554 return self; 555} 556 557- (void)onDisconnect 558{ 559 self.connected = NO; 560 self.ready = NO; 561 562 if ( _inputReports ) 563 { 564 RingBuffer_free( _inputReports ); 565 _inputReports = NULL; 566 } 567} 568 569- (void)setConnected:(bool)connected 570{ 571 _connected = connected; 572 if ( _connected ) 573 { 574 [_bleSteamController discoverServices:nil]; 575 } 576 else 577 { 578 NSLog( @"Disconnected" ); 579 } 580} 581 582- (size_t)read_input_report:(uint8_t *)dst 583{ 584 if ( RingBuffer_read( _inputReports, dst+1 ) ) 585 { 586 switch ( self.pid ) 587 { 588 case D0G_BLE2_PID: 589 *dst = 0x03; 590 break; 591 case TRITON_BLE_PID: 592 *dst = 0x42; 593 break; 594 default: 595 abort(); 596 } 597 return _inputReports->_cbElem + 1; 598 } 599 return 0; 600} 601 602- (int)send_report:(const uint8_t *)data length:(size_t)length 603{ 604 if ( self.pid == D0G_BLE2_PID ) 605 { 606 [_bleSteamController writeValue:[NSData dataWithBytes:data length:length] forCharacteristic:_bleCharacteristicReport type:CBCharacteristicWriteWithResponse]; 607 return (int)length; 608} 609 610 // We need to look up the correct characteristic for this output report 611 if ( length > 0 ) 612 { 613 CBCharacteristic *aChar = [_outputReports objectForKey:[NSNumber numberWithInt:data[0]]]; 614 if ( aChar != nil ) 615 { 616 [_bleSteamController writeValue:[NSData dataWithBytes:&data[1] length:(length - 1)] forCharacteristic:aChar type:CBCharacteristicWriteWithResponse]; 617 return (int)length; 618 } 619 } 620 return -1; 621} 622 623- (int)send_feature_report:(hidFeatureReport *)report length:(size_t)length 624{ 625#if FEATURE_REPORT_LOGGING 626 uint8_t *reportBytes = (uint8_t *)report; 627 628 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, 629 reportBytes[1], reportBytes[2], reportBytes[3], reportBytes[4], reportBytes[5], reportBytes[6], 630 reportBytes[7], reportBytes[8], reportBytes[9], reportBytes[10], reportBytes[11], reportBytes[12], 631 reportBytes[13], reportBytes[14], reportBytes[15], reportBytes[16], reportBytes[17], reportBytes[18], 632 reportBytes[19] ); 633#endif 634 635#if 1 636 // fire-and-forget - we are going to not wait for the response here because all Steam Controller BLE send_feature_report's are ignored, 637 // except errors. 638 [_bleSteamController writeValue:[NSData dataWithBytes:&report->segment length:MIN(length, 64)] forCharacteristic:_bleCharacteristicReport type:CBCharacteristicWriteWithResponse]; 639 640 // pretend we received a result anybody cares about 641 return (int)length; 642 643#else 644 // this is technically the correct send_feature_report logic if you want to make sure it gets through and is 645 // acknowledged or errors out 646 _waitStateForWriteFeatureReport = BLEDeviceWaitState_Waiting; 647 [_bleSteamController writeValue:[NSData dataWithBytes:&report->segment length:MIN(length, 64) 648 ] forCharacteristic:_bleCharacteristicReport type:CBCharacteristicWriteWithResponse]; 649 650 while ( _connected && _waitStateForWriteFeatureReport == BLEDeviceWaitState_Waiting ) 651 { 652 process_pending_events(); 653 } 654 655 if ( !_connected || _waitStateForWriteFeatureReport == BLEDeviceWaitState_Error ) 656 { 657 _waitStateForWriteFeatureReport = BLEDeviceWaitState_None; 658 return -1; 659 } 660 661 _waitStateForWriteFeatureReport = BLEDeviceWaitState_None; 662 return (int)length; 663#endif 664} 665 666- (int)get_feature_report:(uint8_t)feature into:(uint8_t *)buffer length:(size_t)length 667{ 668 _waitStateForReadFeatureReport = BLEDeviceWaitState_Waiting; 669 [_bleSteamController readValueForCharacteristic:_bleCharacteristicReport]; 670 671 while ( _connected && _waitStateForReadFeatureReport == BLEDeviceWaitState_Waiting ) 672 { 673 process_pending_events(); 674 } 675 676 if ( !_connected || _waitStateForReadFeatureReport == BLEDeviceWaitState_Error ) 677 { 678 _waitStateForReadFeatureReport = BLEDeviceWaitState_None; 679 return -1; 680 } 681 682 int amount = 0; 683 if ( _featureReport.length > 0 ) 684 { 685 uint8_t *data = (uint8_t *)_featureReport.bytes; 686 if ( *data == *buffer ) 687 { 688 amount = (int)MIN( length, _featureReport.length ); 689 memcpy( buffer, _featureReport.bytes, amount ); 690 } 691 else 692 { 693 // Leave the report in the buffer 694 amount = (int)MIN( length - 1, _featureReport.length ); 695 memcpy( &buffer[ 1 ], _featureReport.bytes, amount ); 696 ++amount; 697 } 698 } 699 700 _waitStateForReadFeatureReport = BLEDeviceWaitState_None; 701 702#if FEATURE_REPORT_LOGGING 703 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]", 704 _featureReport.length, length, 705 buffer[1], buffer[2], buffer[3], buffer[4], buffer[5], buffer[6], 706 buffer[7], buffer[8], buffer[9], buffer[10], buffer[11], buffer[12], 707 buffer[13], buffer[14], buffer[15], buffer[16], buffer[17], buffer[18], 708 buffer[19] ); 709#endif 710 711 return amount; 712} 713 714#pragma mark CBPeripheralDelegate Implementation 715 716- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error 717{ 718 for (CBService *service in peripheral.services) 719 { 720 NSLog( @"Found Service: %@", service ); 721 if ( [service.UUID isEqual:[CBUUID UUIDWithString:VALVE_SERVICE]] ) 722 { 723 [peripheral discoverCharacteristics:nil forService:service]; 724 } 725 } 726} 727 728- (void)peripheral:(CBPeripheral *)peripheral didDiscoverDescriptorsForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error 729{ 730 // nothing yet needed here, enable for logging 731 if ( /* DISABLES CODE */ (0) ) 732 { 733 for ( CBDescriptor *descriptor in characteristic.descriptors ) 734 { 735 NSLog( @" - Descriptor '%@'", descriptor ); 736 } 737 } 738} 739 740- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error 741{ 742 if ([service.UUID isEqual:[CBUUID UUIDWithString:VALVE_SERVICE]]) 743 { 744 for (CBCharacteristic *aChar in service.characteristics) 745 { 746 NSLog( @"Found Characteristic %@", aChar ); 747 748 if ( [aChar.UUID isEqual:[CBUUID UUIDWithString:VALVE_INPUT_CHAR_0x1106]] ) 749 { 750 self.pid = D0G_BLE2_PID; 751 self.bleCharacteristicInput = aChar; 752 } 753 else if ( [aChar.UUID isEqual:[CBUUID UUIDWithString:VALVE_INPUT_CHAR_0x1303]] ) 754 { 755 self.pid = TRITON_BLE_PID; 756 self.bleCharacteristicInput = aChar; 757 } 758 else if ( [aChar.UUID isEqual:[CBUUID UUIDWithString:VALVE_REPORT_CHAR]] ) 759 { 760 self.bleCharacteristicReport = aChar; 761 [self.bleSteamController discoverDescriptorsForCharacteristic: aChar]; 762 } 763 else 764 { 765 NSString *UUIDString = [aChar.UUID UUIDString]; 766 int report_id = 0; 767 if ( sscanf( UUIDString.UTF8String, "100F6C%x", &report_id ) == 1 && report_id > 0x35 ) 768 { 769 report_id -= 0x35; 770 //NSLog( @"Found characteristic for output report 0x%.2x", report_id ); 771 772 if (report_id >= 0x80) { 773 // An output report 774 [_outputReports setObject:aChar forKey:[NSNumber numberWithInt:report_id]]; 775 } 776 } 777 } 778 } 779 } 780} 781 782- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error 783{ 784 static uint64_t s_ticksLastOverflowReport = 0; 785 786 // receiving an input report is the final indicator that the user accepted a pairing 787 // request and that we successfully established notification. CoreBluetooth has no 788 // notification of the pairing acknowledgement, which is a bad oversight. 789 if ( self.ready == NO ) 790 { 791 self.ready = YES; 792 if ( _inputReports == NULL ) 793 { 794 int cbElem = 0; 795 switch ( self.pid ) 796 { 797 case D0G_BLE2_PID: 798 cbElem = 19; 799 break; 800 case TRITON_BLE_PID: 801 cbElem = 53; 802 break; 803 default: 804 abort(); 805 } 806 _inputReports = RingBuffer_alloc( cbElem ); 807 } 808 HIDBLEManager.sharedInstance.nPendingPairs -= 1; 809 } 810 811 if ( [characteristic.UUID isEqual:_bleCharacteristicInput.UUID] ) 812 { 813 NSData *data = [characteristic value]; 814 if ( _inputReports && data.length != _inputReports->_cbElem ) 815 { 816 NSLog( @"HIDBLE: incoming data is %lu bytes should be exactly %d", (unsigned long)data.length, _inputReports->_cbElem ); 817 } 818 if ( !RingBuffer_write( _inputReports, (const uint8_t *)data.bytes ) ) 819 { 820 uint64_t ticksNow = mach_approximate_time(); 821 if ( ticksNow - s_ticksLastOverflowReport > (5ull * NSEC_PER_SEC / 10) ) 822 { 823 NSLog( @"HIDBLE: input report buffer overflow" ); 824 s_ticksLastOverflowReport = ticksNow; 825 } 826 } 827 } 828 else if ( [characteristic.UUID isEqual:_bleCharacteristicReport.UUID] ) 829 { 830 if ( error != nil ) 831 { 832 NSLog( @"HIDBLE: get_feature_report error: %@", error ); 833 _waitStateForReadFeatureReport = BLEDeviceWaitState_Error; 834 } 835 else 836 { 837 _featureReport = [characteristic value]; 838 _waitStateForReadFeatureReport = BLEDeviceWaitState_Complete; 839 } 840 } 841} 842 843- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error 844{ 845 if ( [characteristic.UUID isEqual:[CBUUID UUIDWithString:VALVE_REPORT_CHAR]] ) 846 { 847 if ( error != nil ) 848 { 849 NSLog( @"HIDBLE: write_feature_report error: %@", error ); 850 _waitStateForWriteFeatureReport = BLEDeviceWaitState_Error; 851 } 852 else 853 { 854 _waitStateForWriteFeatureReport = BLEDeviceWaitState_Complete; 855 } 856 } 857} 858 859- (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error 860{ 861 NSLog( @"didUpdateNotificationStateForCharacteristic %@ (%@)", characteristic, error ); 862} 863 864@end 865 866 867#pragma mark hid_api implementation 868 869struct hid_device_ { 870 void *device_handle; 871 int blocking; 872 struct hid_device_info* device_info; 873 hid_device *next; 874}; 875 876int HID_API_EXPORT HID_API_CALL hid_init(void) 877{ 878 return ( HIDBLEManager.sharedInstance == nil ) ? -1 : 0; 879} 880 881int HID_API_EXPORT HID_API_CALL hid_exit(void) 882{ 883 return 0; 884} 885 886void HID_API_EXPORT HID_API_CALL hid_ble_scan( int bStart ) 887{ 888 HIDBLEManager *bleManager = HIDBLEManager.sharedInstance; 889 if ( bStart ) 890 { 891 [bleManager startScan:0]; 892 } 893 else 894 { 895 [bleManager stopScan]; 896 } 897} 898 899HID_API_EXPORT hid_device * HID_API_CALL hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number) 900{ 901 return NULL; 902} 903 904HID_API_EXPORT hid_device * HID_API_CALL hid_open_path( const char *path ) 905{ 906 hid_device *result = NULL; 907 NSString *nssPath = [NSString stringWithUTF8String:path]; 908 HIDBLEManager *bleManager = HIDBLEManager.sharedInstance; 909 NSEnumerator<HIDBLEDevice *> *devices = [bleManager.deviceMap objectEnumerator]; 910 911 for ( HIDBLEDevice *device in devices ) 912 { 913 // we have the device but it hasn't found its service or characteristics until it is connected 914 if ( !device.ready || !device.connected || !device.bleCharacteristicInput ) 915 continue; 916 917 if ( [device.bleSteamController.identifier.UUIDString isEqualToString:nssPath] ) 918 { 919 result = (hid_device *)malloc( sizeof( hid_device ) ); 920 memset( result, 0, sizeof( hid_device ) ); 921 result->device_handle = (void*)CFBridgingRetain( device ); 922 result->blocking = NO; 923 // enable reporting input events on the characteristic 924 [device.bleSteamController setNotifyValue:YES forCharacteristic:device.bleCharacteristicInput]; 925 return result; 926 } 927 } 928 return result; 929} 930 931void HID_API_EXPORT hid_free_enumeration(struct hid_device_info *devs) 932{ 933 /* This function is identical to the Linux version. Platform independent. */ 934 struct hid_device_info *d = devs; 935 while (d) { 936 struct hid_device_info *next = d->next; 937 free(d->path); 938 free(d->serial_number); 939 free(d->manufacturer_string); 940 free(d->product_string); 941 free(d); 942 d = next; 943 } 944} 945 946int HID_API_EXPORT hid_set_nonblocking(hid_device *dev, int nonblock) 947{ 948 /* All Nonblocking operation is handled by the library. */ 949 dev->blocking = !nonblock; 950 951 return 0; 952} 953 954static struct hid_device_info *create_device_info_for_hid_device(HIDBLEDevice *device) 955{ 956 // We currently only support the Steam Controller 957 struct hid_device_info *device_info = (struct hid_device_info *)malloc( sizeof(struct hid_device_info) ); 958 memset( device_info, 0, sizeof(struct hid_device_info) ); 959 device_info->path = strdup( device.bleSteamController.identifier.UUIDString.UTF8String ); 960 device_info->vendor_id = VALVE_USB_VID; 961 device_info->product_id = device.pid; 962 device_info->product_string = wcsdup( L"Steam Controller" ); 963 device_info->manufacturer_string = wcsdup( L"Valve Corporation" ); 964 device_info->bus_type = HID_API_BUS_BLUETOOTH; 965 return device_info; 966} 967 968struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, unsigned short product_id) 969{ @autoreleasepool { 970 struct hid_device_info *root = NULL; 971 972 HIDBLEManager *bleManager = HIDBLEManager.sharedInstance; 973 [bleManager updateConnectedSteamControllers:false]; 974 NSEnumerator<HIDBLEDevice *> *devices = [bleManager.deviceMap objectEnumerator]; 975 for ( HIDBLEDevice *device in devices ) 976 { 977 // there are several brief windows in connecting to an already paired device and 978 // one long window waiting for users to confirm pairing where we don't want 979 // to consider a device ready - if we hand it back to SDL or another 980 // Steam Controller consumer, their additional SC setup work will fail 981 // in unusual/silent ways and we can actually corrupt the BLE stack for 982 // the entire system and kill the appletv remote's Menu button (!) 983 if ( device.bleSteamController.state != CBPeripheralStateConnected || 984 device.connected == NO || device.ready == NO ) 985 { 986 if ( device.ready == NO && device.bleCharacteristicInput != nil ) 987 { 988 // attempt to register for input reports. this call will silently fail 989 // until the pairing finalizes with user acceptance. oh, apple. 990 [device.bleSteamController setNotifyValue:YES forCharacteristic:device.bleCharacteristicInput]; 991 } 992 continue; 993 } 994 995 if ( ( vendor_id != 0 && vendor_id != VALVE_USB_VID ) || 996 ( product_id != 0 && product_id != device.pid ) ) 997 { 998 continue; 999 } 1000 1001 /* See if there are any devices we should skip in enumeration */ 1002 if (SDL_HIDAPI_ShouldIgnoreDevice(HID_API_BUS_BLUETOOTH, VALVE_USB_VID, device.pid, 0, 0)) { 1003 continue; 1004 } 1005 1006 struct hid_device_info *device_info = create_device_info_for_hid_device(device); 1007 device_info->next = root; 1008 root = device_info; 1009 } 1010 return root; 1011}} 1012 1013int HID_API_EXPORT_CALL hid_get_manufacturer_string(hid_device *dev, wchar_t *string, size_t maxlen) 1014{ 1015 static wchar_t s_wszManufacturer[] = L"Valve Corporation"; 1016 wcsncpy( string, s_wszManufacturer, sizeof(s_wszManufacturer)/sizeof(s_wszManufacturer[0]) ); 1017 return 0; 1018} 1019 1020int HID_API_EXPORT_CALL hid_get_product_string(hid_device *dev, wchar_t *string, size_t maxlen) 1021{ 1022 static wchar_t s_wszProduct[] = L"Steam Controller"; 1023 wcsncpy( string, s_wszProduct, sizeof(s_wszProduct)/sizeof(s_wszProduct[0]) ); 1024 return 0; 1025} 1026 1027int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen) 1028{ 1029 static wchar_t s_wszSerial[] = L"12345"; 1030 wcsncpy( string, s_wszSerial, sizeof(s_wszSerial)/sizeof(s_wszSerial[0]) ); 1031 return 0; 1032} 1033 1034int HID_API_EXPORT_CALL hid_get_indexed_string(hid_device *dev, int string_index, wchar_t *string, size_t maxlen) 1035{ 1036 return -1; 1037} 1038 1039struct hid_device_info *hid_get_device_info(hid_device *dev) 1040{ 1041 HIDBLEDevice *device_handle = (__bridge HIDBLEDevice *)dev->device_handle; 1042 1043 if (!dev->device_info) { 1044 // Lazy initialize device_info 1045 dev->device_info = create_device_info_for_hid_device(device_handle); 1046 } 1047 1048 // create_device_info_for_hid_device will set an error if needed 1049 return dev->device_info; 1050} 1051 1052int hid_get_report_descriptor(hid_device *device, unsigned char *buf, size_t buf_size) 1053{ 1054 // Not implemented 1055 return -1; 1056} 1057 1058int HID_API_EXPORT hid_write(hid_device *dev, const unsigned char *data, size_t length) 1059{ 1060 HIDBLEDevice *device_handle = (__bridge HIDBLEDevice *)dev->device_handle; 1061 1062 if ( !device_handle.connected ) 1063 return -1; 1064 1065 return [device_handle send_report:data length:length]; 1066} 1067 1068void HID_API_EXPORT hid_close(hid_device *dev) 1069{ 1070 HIDBLEDevice *device_handle = CFBridgingRelease( dev->device_handle ); 1071 1072 // disable reporting input events on the characteristic 1073 if ( device_handle.connected ) { 1074 [device_handle.bleSteamController setNotifyValue:NO forCharacteristic:device_handle.bleCharacteristicInput]; 1075 } 1076 1077 hid_free_enumeration(dev->device_info); 1078 1079 free( dev ); 1080} 1081 1082int HID_API_EXPORT hid_send_feature_report(hid_device *dev, const unsigned char *data, size_t length) 1083{ 1084 HIDBLEDevice *device_handle = (__bridge HIDBLEDevice *)dev->device_handle; 1085 1086 if ( !device_handle.connected ) 1087 return -1; 1088 1089 return [device_handle send_feature_report:(hidFeatureReport *)(void *)data length:length]; 1090} 1091 1092int HID_API_EXPORT hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length) 1093{ 1094 HIDBLEDevice *device_handle = (__bridge HIDBLEDevice *)dev->device_handle; 1095 1096 if ( !device_handle.connected ) 1097 return -1; 1098 1099 size_t written = [device_handle get_feature_report:data[0] into:data length:length]; 1100 1101 return written == length-1 ? (int)length : (int)written; 1102} 1103 1104int HID_API_EXPORT hid_get_input_report(hid_device *dev, unsigned char *data, size_t length) 1105{ 1106 // Not supported 1107 return -1; 1108} 1109 1110int HID_API_EXPORT hid_read(hid_device *dev, unsigned char *data, size_t length) 1111{ 1112 HIDBLEDevice *device_handle = (__bridge HIDBLEDevice *)dev->device_handle; 1113 1114 if ( !device_handle.connected ) 1115 return -1; 1116 1117 return hid_read_timeout(dev, data, length, 0); 1118} 1119 1120int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t length, int milliseconds) 1121{ 1122 HIDBLEDevice *device_handle = (__bridge HIDBLEDevice *)dev->device_handle; 1123 1124 if ( !device_handle.connected ) 1125 return -1; 1126 1127 if ( milliseconds != 0 ) 1128 { 1129 NSLog( @"hid_read_timeout with non-zero wait" ); 1130 } 1131 int result = (int)[device_handle read_input_report:data]; 1132#if 0 //FEATURE_REPORT_LOGGING 1133 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, 1134 data[1], data[2], data[3], data[4], data[5], data[6], 1135 data[7], data[8], data[9], data[10], data[11], data[12], 1136 data[13], data[14], data[15], data[16], data[17], data[18], 1137 data[19] ); 1138#endif 1139 return result; 1140} 1141 1142HID_API_EXPORT const wchar_t* HID_API_CALL hid_error(hid_device *dev) 1143{ 1144 return NULL; 1145} 1146 1147#endif /* !SDL_HIDAPI_DISABLED */ 1148 1149#endif /* SDL_PLATFORM_IOS || SDL_PLATFORM_TVOS */ 1150[FILE END](C) 2025 0x4248 (C) 2025 4248 Media and 4248 Systems, All part of 0x4248 See LICENCE files for more information. Not all files are by 0x4248 always check Licencing.