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