SDL

Форк
0
/
hid.m 
1037 строк · 32.8 Кб
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 isEqualToString:@"SteamController"] )
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 CBCentralManagerStatePoweredOn:
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 CBCentralManagerStatePoweredOff:
422
			NSLog( @"CoreBluetooth BLE hardware is powered off" );
423
			break;
424

425
		case CBCentralManagerStateUnauthorized:
426
			NSLog( @"CoreBluetooth BLE state is unauthorized" );
427
			break;
428

429
		case CBCentralManagerStateUnknown:
430
			NSLog( @"CoreBluetooth BLE state is unknown" );
431
			break;
432

433
		case CBCentralManagerStateUnsupported:
434
			NSLog( @"CoreBluetooth BLE hardware is unsupported on this platform" );
435
			break;
436

437
		case CBCentralManagerStateResetting:
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 isEqualToString:@"SteamController"] )
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
    return device_info;
857
}
858

859
struct hid_device_info  HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, unsigned short product_id)
860
{ @autoreleasepool {
861
	struct hid_device_info *root = NULL;
862

863
	/* See if there are any devices we should skip in enumeration */
864
	if (SDL_HIDAPI_ShouldIgnoreDevice(HID_API_BUS_BLUETOOTH, VALVE_USB_VID, D0G_BLE2_PID, 0, 0)) {
865
		return NULL;
866
	}
867

868
	if ( ( vendor_id == 0 || vendor_id == VALVE_USB_VID ) &&
869
	     ( product_id == 0 || product_id == D0G_BLE2_PID ) )
870
	{
871
		HIDBLEManager *bleManager = HIDBLEManager.sharedInstance;
872
		[bleManager updateConnectedSteamControllers:false];
873
		NSEnumerator<HIDBLEDevice *> *devices = [bleManager.deviceMap objectEnumerator];
874
		for ( HIDBLEDevice *device in devices )
875
		{
876
			// there are several brief windows in connecting to an already paired device and
877
			// one long window waiting for users to confirm pairing where we don't want
878
			// to consider a device ready - if we hand it back to SDL or another
879
			// Steam Controller consumer, their additional SC setup work will fail
880
			// in unusual/silent ways and we can actually corrupt the BLE stack for
881
			// the entire system and kill the appletv remote's Menu button (!)
882
			if ( device.bleSteamController.state != CBPeripheralStateConnected ||
883
				 device.connected == NO || device.ready == NO )
884
			{
885
				if ( device.ready == NO && device.bleCharacteristicInput != nil )
886
				{
887
					// attempt to register for input reports. this call will silently fail
888
					// until the pairing finalizes with user acceptance. oh, apple.
889
					[device.bleSteamController setNotifyValue:YES forCharacteristic:device.bleCharacteristicInput];
890
				}
891
				continue;
892
			}
893
			struct hid_device_info *device_info = create_device_info_for_hid_device(device);
894
			device_info->next = root;
895
			root = device_info;
896
		}
897
	}
898
	return root;
899
}}
900

901
int HID_API_EXPORT_CALL hid_get_manufacturer_string(hid_device *dev, wchar_t *string, size_t maxlen)
902
{
903
	static wchar_t s_wszManufacturer[] = L"Valve Corporation";
904
	wcsncpy( string, s_wszManufacturer, sizeof(s_wszManufacturer)/sizeof(s_wszManufacturer[0]) );
905
	return 0;
906
}
907

908
int HID_API_EXPORT_CALL hid_get_product_string(hid_device *dev, wchar_t *string, size_t maxlen)
909
{
910
	static wchar_t s_wszProduct[] = L"Steam Controller";
911
	wcsncpy( string, s_wszProduct, sizeof(s_wszProduct)/sizeof(s_wszProduct[0]) );
912
	return 0;
913
}
914

915
int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen)
916
{
917
	static wchar_t s_wszSerial[] = L"12345";
918
	wcsncpy( string, s_wszSerial, sizeof(s_wszSerial)/sizeof(s_wszSerial[0]) );
919
	return 0;
920
}
921

922
int HID_API_EXPORT_CALL hid_get_indexed_string(hid_device *dev, int string_index, wchar_t *string, size_t maxlen)
923
{
924
	return -1;
925
}
926

927
struct hid_device_info *hid_get_device_info(hid_device *dev)
928
{
929
    HIDBLEDevice *device_handle = (__bridge HIDBLEDevice *)dev->device_handle;
930

931
	if (!dev->device_info) {
932
		// Lazy initialize device_info
933
		dev->device_info = create_device_info_for_hid_device(device_handle);
934
	}
935

936
	// create_device_info_for_hid_device will set an error if needed
937
	return dev->device_info;
938
}
939

940
int hid_get_report_descriptor(hid_device *device, unsigned char *buf, size_t buf_size)
941
{
942
    // Not implemented
943
    return -1;
944
}
945

946
int HID_API_EXPORT hid_write(hid_device *dev, const unsigned char *data, size_t length)
947
{
948
    HIDBLEDevice *device_handle = (__bridge HIDBLEDevice *)dev->device_handle;
949

950
	if ( !device_handle.connected )
951
		return -1;
952

953
	return [device_handle send_report:data length:length];
954
}
955

956
void HID_API_EXPORT hid_close(hid_device *dev)
957
{
958
    HIDBLEDevice *device_handle = CFBridgingRelease( dev->device_handle );
959

960
	// disable reporting input events on the characteristic
961
	if ( device_handle.connected ) {
962
		[device_handle.bleSteamController setNotifyValue:NO forCharacteristic:device_handle.bleCharacteristicInput];
963
	}
964

965
    hid_free_enumeration(dev->device_info);
966

967
	free( dev );
968
}
969

970
int HID_API_EXPORT hid_send_feature_report(hid_device *dev, const unsigned char *data, size_t length)
971
{
972
    HIDBLEDevice *device_handle = (__bridge HIDBLEDevice *)dev->device_handle;
973

974
	if ( !device_handle.connected )
975
		return -1;
976

977
	return [device_handle send_feature_report:(hidFeatureReport *)(void *)data];
978
}
979

980
int HID_API_EXPORT hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length)
981
{
982
    HIDBLEDevice *device_handle = (__bridge HIDBLEDevice *)dev->device_handle;
983

984
	if ( !device_handle.connected )
985
		return -1;
986

987
	size_t written = [device_handle get_feature_report:data[0] into:data];
988

989
	return written == length-1 ? (int)length : (int)written;
990
}
991

992
int HID_API_EXPORT hid_get_input_report(hid_device *dev, unsigned char *data, size_t length)
993
{
994
    // Not supported
995
    return -1;
996
}
997

998
int HID_API_EXPORT hid_read(hid_device *dev, unsigned char *data, size_t length)
999
{
1000
    HIDBLEDevice *device_handle = (__bridge HIDBLEDevice *)dev->device_handle;
1001

1002
	if ( !device_handle.connected )
1003
		return -1;
1004

1005
	return hid_read_timeout(dev, data, length, 0);
1006
}
1007

1008
int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t length, int milliseconds)
1009
{
1010
    HIDBLEDevice *device_handle = (__bridge HIDBLEDevice *)dev->device_handle;
1011

1012
	if ( !device_handle.connected )
1013
		return -1;
1014

1015
	if ( milliseconds != 0 )
1016
	{
1017
		NSLog( @"hid_read_timeout with non-zero wait" );
1018
	}
1019
	int result = (int)[device_handle read_input_report:data];
1020
#if FEATURE_REPORT_LOGGING
1021
	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,
1022
		  data[1], data[2], data[3], data[4], data[5], data[6],
1023
		  data[7], data[8], data[9], data[10], data[11], data[12],
1024
		  data[13], data[14], data[15], data[16], data[17], data[18],
1025
		  data[19] );
1026
#endif
1027
	return result;
1028
}
1029

1030
HID_API_EXPORT const wchar_t* HID_API_CALL hid_error(hid_device *dev)
1031
{
1032
	return NULL;
1033
}
1034

1035
#endif /* !SDL_HIDAPI_DISABLED */
1036

1037
#endif /* SDL_PLATFORM_IOS || SDL_PLATFORM_TVOS */
1038

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.