mirror of
https://github.com/libsdl-org/SDL.git
synced 2025-10-07 18:36:26 +00:00
Added missing files from previous commits
This commit is contained in:
32
src/hidapi/ios/Makefile-manual
Normal file
32
src/hidapi/ios/Makefile-manual
Normal file
@@ -0,0 +1,32 @@
|
||||
###########################################
|
||||
# Simple Makefile for HIDAPI test program
|
||||
#
|
||||
# Alan Ott
|
||||
# Signal 11 Software
|
||||
# 2010-07-03
|
||||
###########################################
|
||||
|
||||
all: hidtest
|
||||
|
||||
CC=gcc
|
||||
CXX=g++
|
||||
COBJS=hid.o
|
||||
CPPOBJS=../hidtest/hidtest.o
|
||||
OBJS=$(COBJS) $(CPPOBJS)
|
||||
CFLAGS+=-I../hidapi -Wall -g -c
|
||||
LIBS=-framework CoreBluetooth -framework CoreFoundation
|
||||
|
||||
|
||||
hidtest: $(OBJS)
|
||||
g++ -Wall -g $^ $(LIBS) -o hidtest
|
||||
|
||||
$(COBJS): %.o: %.c
|
||||
$(CC) $(CFLAGS) $< -o $@
|
||||
|
||||
$(CPPOBJS): %.o: %.cpp
|
||||
$(CXX) $(CFLAGS) $< -o $@
|
||||
|
||||
clean:
|
||||
rm -f *.o hidtest $(CPPOBJS)
|
||||
|
||||
.PHONY: clean
|
9
src/hidapi/ios/Makefile.am
Normal file
9
src/hidapi/ios/Makefile.am
Normal file
@@ -0,0 +1,9 @@
|
||||
lib_LTLIBRARIES = libhidapi.la
|
||||
libhidapi_la_SOURCES = hid.m
|
||||
libhidapi_la_LDFLAGS = $(LTLDFLAGS)
|
||||
AM_CPPFLAGS = -I$(top_srcdir)/hidapi/
|
||||
|
||||
hdrdir = $(includedir)/hidapi
|
||||
hdr_HEADERS = $(top_srcdir)/hidapi/hidapi.h
|
||||
|
||||
EXTRA_DIST = Makefile-manual
|
883
src/hidapi/ios/hid.mm
Normal file
883
src/hidapi/ios/hid.mm
Normal file
@@ -0,0 +1,883 @@
|
||||
//======== Copyright (c) 2017 Valve Corporation, All rights reserved. =========
|
||||
//
|
||||
// Purpose: HID device abstraction temporary stub
|
||||
//
|
||||
//=============================================================================
|
||||
|
||||
#include <CoreBluetooth/CoreBluetooth.h>
|
||||
#include <QuartzCore/QuartzCore.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <mach/mach_time.h>
|
||||
#include <pthread.h>
|
||||
#include <sys/time.h>
|
||||
#include <unistd.h>
|
||||
#include "../hidapi/hidapi.h"
|
||||
|
||||
#define VALVE_USB_VID 0x28DE
|
||||
#define D0G_BLE2_PID 0x1106
|
||||
|
||||
typedef uint32_t uint32;
|
||||
typedef uint64_t uint64;
|
||||
|
||||
// enables detailed NSLog logging of feature reports
|
||||
#define FEATURE_REPORT_LOGGING 0
|
||||
|
||||
#define REPORT_SEGMENT_DATA_FLAG 0x80
|
||||
#define REPORT_SEGMENT_LAST_FLAG 0x40
|
||||
|
||||
#define VALVE_SERVICE @"100F6C32-1735-4313-B402-38567131E5F3"
|
||||
|
||||
// (READ/NOTIFICATIONS)
|
||||
#define VALVE_INPUT_CHAR @"100F6C33-1735-4313-B402-38567131E5F3"
|
||||
|
||||
// (READ/WRITE)
|
||||
#define VALVE_REPORT_CHAR @"100F6C34-1735-4313-B402-38567131E5F3"
|
||||
|
||||
// TODO: create CBUUID's in __attribute__((constructor)) rather than doing [CBUUID UUIDWithString:...] everywhere
|
||||
|
||||
#pragma pack(push,1)
|
||||
struct bluetoothSegment {
|
||||
uint8_t segmentHeader;
|
||||
uint8_t featureReportMessageID;
|
||||
uint8_t length;
|
||||
uint8_t settingIdentifier;
|
||||
union {
|
||||
uint16_t usPayload;
|
||||
uint32_t uPayload;
|
||||
uint64_t ulPayload;
|
||||
uint8_t ucPayload[15];
|
||||
};
|
||||
|
||||
size_t size() { return length + 3; }
|
||||
};
|
||||
|
||||
struct hidFeatureReport {
|
||||
uint8_t id;
|
||||
union {
|
||||
bluetoothSegment segment;
|
||||
struct {
|
||||
uint8_t segmentHeader;
|
||||
uint8_t featureReportMessageID;
|
||||
uint8_t length;
|
||||
uint8_t settingIdentifier;
|
||||
union {
|
||||
uint16_t usPayload;
|
||||
uint32_t uPayload;
|
||||
uint64_t ulPayload;
|
||||
uint8_t ucPayload[15];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
template <typename T, size_t cbElem, size_t nElem>
|
||||
struct RingBuffer {
|
||||
int _first, _last;
|
||||
uint8_t _data[ ( nElem * cbElem ) ];
|
||||
pthread_mutex_t accessLock;
|
||||
|
||||
RingBuffer() { _first = -1; _last = 0; pthread_mutex_init( &accessLock, 0 ); }
|
||||
|
||||
bool write( const T *src )
|
||||
{
|
||||
pthread_mutex_lock( &accessLock );
|
||||
memcpy( &_data[ _last ], src, cbElem );
|
||||
if ( _first == -1 )
|
||||
{
|
||||
_first = _last;
|
||||
}
|
||||
_last = ( _last + cbElem ) % (nElem * cbElem);
|
||||
if ( _last == _first )
|
||||
{
|
||||
_first = ( _first + cbElem ) % (nElem * cbElem);
|
||||
pthread_mutex_unlock( &accessLock );
|
||||
return false;
|
||||
}
|
||||
pthread_mutex_unlock( &accessLock );
|
||||
return true;
|
||||
}
|
||||
|
||||
bool read( T *dst )
|
||||
{
|
||||
pthread_mutex_lock( &accessLock );
|
||||
if ( _first == -1 )
|
||||
{
|
||||
pthread_mutex_unlock( &accessLock );
|
||||
return false;
|
||||
}
|
||||
memcpy( dst, &_data[ _first ], cbElem );
|
||||
_first = ( _first + cbElem ) % (nElem * cbElem);
|
||||
if ( _first == _last )
|
||||
{
|
||||
_first = -1;
|
||||
}
|
||||
pthread_mutex_unlock( &accessLock );
|
||||
return true;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
#pragma mark HIDBLEDevice Definition
|
||||
|
||||
enum BLEDeviceWaitState
|
||||
{
|
||||
None,
|
||||
Waiting,
|
||||
Complete,
|
||||
Error
|
||||
};
|
||||
|
||||
@interface HIDBLEDevice : NSObject <CBPeripheralDelegate>
|
||||
{
|
||||
RingBuffer<uint8_t, 19, 4096> _inputReports;
|
||||
uint8_t _featureReport[20];
|
||||
BLEDeviceWaitState _waitStateForReadFeatureReport;
|
||||
BLEDeviceWaitState _waitStateForWriteFeatureReport;
|
||||
}
|
||||
|
||||
@property (nonatomic, readwrite) bool connected;
|
||||
@property (nonatomic, readwrite) bool ready;
|
||||
|
||||
@property (nonatomic, strong) CBPeripheral *bleSteamController;
|
||||
@property (nonatomic, strong) CBCharacteristic *bleCharacteristicInput;
|
||||
@property (nonatomic, strong) CBCharacteristic *bleCharacteristicReport;
|
||||
|
||||
- (id)initWithPeripheral:(CBPeripheral *)peripheral;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface HIDBLEManager : NSObject <CBCentralManagerDelegate>
|
||||
|
||||
@property (nonatomic) int nPendingScans;
|
||||
@property (nonatomic) int nPendingPairs;
|
||||
@property (nonatomic, strong) CBCentralManager *centralManager;
|
||||
@property (nonatomic, strong) NSMapTable<CBPeripheral *, HIDBLEDevice *> *deviceMap;
|
||||
@property (nonatomic, retain) dispatch_queue_t bleSerialQueue;
|
||||
|
||||
+ (instancetype)sharedInstance;
|
||||
- (void)startScan:(int)duration;
|
||||
- (void)stopScan;
|
||||
- (int)updateConnectedSteamControllers:(BOOL) bForce;
|
||||
- (void)appWillResignActiveNotification:(NSNotification *)note;
|
||||
- (void)appDidBecomeActiveNotification:(NSNotification *)note;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
// singleton class - access using HIDBLEManager.sharedInstance
|
||||
@implementation HIDBLEManager
|
||||
|
||||
+ (instancetype)sharedInstance
|
||||
{
|
||||
static HIDBLEManager *sharedInstance = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
sharedInstance = [HIDBLEManager new];
|
||||
sharedInstance.nPendingScans = 0;
|
||||
sharedInstance.nPendingPairs = 0;
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:sharedInstance selector:@selector(appWillResignActiveNotification:) name: UIApplicationWillResignActiveNotification object:nil];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:sharedInstance selector:@selector(appDidBecomeActiveNotification:) name:UIApplicationDidBecomeActiveNotification object:nil];
|
||||
|
||||
// receive reports on a high-priority serial-queue. optionally put writes on the serial queue to avoid logical
|
||||
// race conditions talking to the controller from multiple threads, although BLE fragmentation/assembly means
|
||||
// that we can still screw this up.
|
||||
// most importantly we need to consume reports at a high priority to avoid the OS thinking we aren't really
|
||||
// listening to the BLE device, as iOS on slower devices may stop delivery of packets to the app WITHOUT ACTUALLY
|
||||
// DISCONNECTING FROM THE DEVICE if we don't react quickly enough to their delivery.
|
||||
// see also the error-handling states in the peripheral delegate to re-open the device if it gets closed
|
||||
sharedInstance.bleSerialQueue = dispatch_queue_create( "com.valvesoftware.steamcontroller.ble", DISPATCH_QUEUE_SERIAL );
|
||||
dispatch_set_target_queue( sharedInstance.bleSerialQueue, dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_HIGH, 0 ) );
|
||||
|
||||
// creating a CBCentralManager will always trigger a future centralManagerDidUpdateState:
|
||||
// where any scanning gets started or connecting to existing peripherals happens, it's never already in a
|
||||
// powered-on state for a newly launched application.
|
||||
sharedInstance.centralManager = [[CBCentralManager alloc] initWithDelegate:sharedInstance queue:sharedInstance.bleSerialQueue];
|
||||
sharedInstance.deviceMap = [[NSMapTable alloc] initWithKeyOptions:NSMapTableWeakMemory valueOptions:NSMapTableStrongMemory capacity:4];
|
||||
});
|
||||
return sharedInstance;
|
||||
}
|
||||
|
||||
// called for NSNotification UIApplicationWillResignActiveNotification
|
||||
- (void)appWillResignActiveNotification:(NSNotification *)note
|
||||
{
|
||||
// we'll get resign-active notification if pairing is happening.
|
||||
if ( self.nPendingPairs > 0 )
|
||||
return;
|
||||
|
||||
for ( CBPeripheral *peripheral in self.deviceMap )
|
||||
{
|
||||
HIDBLEDevice *steamController = [self.deviceMap objectForKey:peripheral];
|
||||
if ( steamController )
|
||||
{
|
||||
steamController.connected = NO;
|
||||
steamController.ready = NO;
|
||||
[self.centralManager cancelPeripheralConnection:peripheral];
|
||||
}
|
||||
}
|
||||
[self.deviceMap removeAllObjects];
|
||||
}
|
||||
|
||||
// called for NSNotification UIApplicationDidBecomeActiveNotification
|
||||
// whenever the application comes back from being inactive, trigger a 20s pairing scan and reconnect
|
||||
// any devices that may have paired while we were inactive.
|
||||
- (void)appDidBecomeActiveNotification:(NSNotification *)note
|
||||
{
|
||||
[self updateConnectedSteamControllers:true];
|
||||
[self startScan:20];
|
||||
}
|
||||
|
||||
- (int)updateConnectedSteamControllers:(BOOL) bForce
|
||||
{
|
||||
static uint64_t s_unLastUpdateTick = 0;
|
||||
static mach_timebase_info_data_t s_timebase_info;
|
||||
|
||||
if (s_timebase_info.denom == 0)
|
||||
{
|
||||
mach_timebase_info( &s_timebase_info );
|
||||
}
|
||||
|
||||
uint64_t ticksNow = mach_approximate_time();
|
||||
if ( !bForce && ( ( (ticksNow - s_unLastUpdateTick) * s_timebase_info.numer ) / s_timebase_info.denom ) < (5ull * NSEC_PER_SEC) )
|
||||
return (int)self.deviceMap.count;
|
||||
|
||||
// we can see previously connected BLE peripherals but can't connect until the CBCentralManager
|
||||
// is fully powered up - only do work when we are in that state
|
||||
if ( self.centralManager.state != CBManagerStatePoweredOn )
|
||||
return (int)self.deviceMap.count;
|
||||
|
||||
// only update our last-check-time if we actually did work, otherwise there can be a long delay during initial power-up
|
||||
s_unLastUpdateTick = mach_approximate_time();
|
||||
|
||||
// if a pair is in-flight, the central manager may still give it back via retrieveConnected... and
|
||||
// cause the SDL layer to attempt to initialize it while some of its endpoints haven't yet been established
|
||||
if ( self.nPendingPairs > 0 )
|
||||
return (int)self.deviceMap.count;
|
||||
|
||||
NSArray<CBPeripheral *> *peripherals = [self.centralManager retrieveConnectedPeripheralsWithServices: @[ [CBUUID UUIDWithString:@"180A"]]];
|
||||
for ( CBPeripheral *peripheral in peripherals )
|
||||
{
|
||||
// we already know this peripheral
|
||||
if ( [self.deviceMap objectForKey: peripheral] != nil )
|
||||
continue;
|
||||
|
||||
NSLog( @"connected peripheral: %@", peripheral );
|
||||
if ( [peripheral.name isEqualToString:@"SteamController"] )
|
||||
{
|
||||
HIDBLEDevice *steamController = [[HIDBLEDevice alloc] initWithPeripheral:peripheral];
|
||||
[self.deviceMap setObject:steamController forKey:peripheral];
|
||||
[self.centralManager connectPeripheral:peripheral options:nil];
|
||||
}
|
||||
}
|
||||
|
||||
return (int)self.deviceMap.count;
|
||||
}
|
||||
|
||||
// manual API for folks to start & stop scanning
|
||||
- (void)startScan:(int)duration
|
||||
{
|
||||
NSLog( @"BLE: requesting scan for %d seconds", duration );
|
||||
@synchronized (self)
|
||||
{
|
||||
if ( _nPendingScans++ == 0 )
|
||||
{
|
||||
[self.centralManager scanForPeripheralsWithServices:nil options:nil];
|
||||
}
|
||||
}
|
||||
|
||||
if ( duration != 0 )
|
||||
{
|
||||
dispatch_after( dispatch_time( DISPATCH_TIME_NOW, (int64_t)(duration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
[self stopScan];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
- (void)stopScan
|
||||
{
|
||||
NSLog( @"BLE: stopping scan" );
|
||||
@synchronized (self)
|
||||
{
|
||||
if ( --_nPendingScans <= 0 )
|
||||
{
|
||||
_nPendingScans = 0;
|
||||
[self.centralManager stopScan];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#pragma mark CBCentralManagerDelegate Implementation
|
||||
|
||||
// called whenever the BLE hardware state changes.
|
||||
- (void)centralManagerDidUpdateState:(CBCentralManager *)central
|
||||
{
|
||||
switch ( central.state )
|
||||
{
|
||||
case CBCentralManagerStatePoweredOn:
|
||||
{
|
||||
NSLog( @"CoreBluetooth BLE hardware is powered on and ready" );
|
||||
|
||||
// at startup, if we have no already attached peripherals, do a 20s scan for new unpaired devices,
|
||||
// otherwise callers should occaisionally do additional scans. we don't want to continuously be
|
||||
// scanning because it drains battery, causes other nearby people to have a hard time pairing their
|
||||
// Steam Controllers, and may also trigger firmware weirdness when a device attempts to start
|
||||
// the pairing sequence multiple times concurrently
|
||||
if ( [self updateConnectedSteamControllers:false] == 0 )
|
||||
{
|
||||
// TODO: we could limit our scan to only peripherals supporting the SteamController service, but
|
||||
// that service doesn't currently fit in the base advertising packet, we'd need to put it into an
|
||||
// extended scan packet. Useful optimization downstream, but not currently necessary
|
||||
// NSArray *services = @[[CBUUID UUIDWithString:VALVE_SERVICE]];
|
||||
[self startScan:20];
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case CBCentralManagerStatePoweredOff:
|
||||
NSLog( @"CoreBluetooth BLE hardware is powered off" );
|
||||
break;
|
||||
|
||||
case CBCentralManagerStateUnauthorized:
|
||||
NSLog( @"CoreBluetooth BLE state is unauthorized" );
|
||||
break;
|
||||
|
||||
case CBCentralManagerStateUnknown:
|
||||
NSLog( @"CoreBluetooth BLE state is unknown" );
|
||||
break;
|
||||
|
||||
case CBCentralManagerStateUnsupported:
|
||||
NSLog( @"CoreBluetooth BLE hardware is unsupported on this platform" );
|
||||
break;
|
||||
|
||||
case CBCentralManagerStateResetting:
|
||||
NSLog( @"CoreBluetooth BLE manager is resetting" );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
|
||||
{
|
||||
HIDBLEDevice *steamController = [_deviceMap objectForKey:peripheral];
|
||||
steamController.connected = YES;
|
||||
self.nPendingPairs -= 1;
|
||||
}
|
||||
|
||||
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
|
||||
{
|
||||
NSLog( @"Failed to connect: %@", error );
|
||||
[_deviceMap removeObjectForKey:peripheral];
|
||||
self.nPendingPairs -= 1;
|
||||
}
|
||||
|
||||
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
|
||||
{
|
||||
NSString *localName = [advertisementData objectForKey:CBAdvertisementDataLocalNameKey];
|
||||
NSString *log = [NSString stringWithFormat:@"Found '%@'", localName];
|
||||
|
||||
if ( [localName isEqualToString:@"SteamController"] )
|
||||
{
|
||||
NSLog( @"%@ : %@ - %@", log, peripheral, advertisementData );
|
||||
self.nPendingPairs += 1;
|
||||
HIDBLEDevice *steamController = [[HIDBLEDevice alloc] initWithPeripheral:peripheral];
|
||||
[self.deviceMap setObject:steamController forKey:peripheral];
|
||||
[self.centralManager connectPeripheral:peripheral options:nil];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
|
||||
{
|
||||
HIDBLEDevice *steamController = [self.deviceMap objectForKey:peripheral];
|
||||
if ( steamController )
|
||||
{
|
||||
steamController.connected = NO;
|
||||
steamController.ready = NO;
|
||||
[self.deviceMap removeObjectForKey:peripheral];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
// Core Bluetooth devices calling back on event boundaries of their run-loops. so annoying.
|
||||
static void process_pending_events()
|
||||
{
|
||||
CFRunLoopRunResult res;
|
||||
do
|
||||
{
|
||||
res = CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0.001, FALSE );
|
||||
}
|
||||
while( res != kCFRunLoopRunFinished && res != kCFRunLoopRunTimedOut );
|
||||
}
|
||||
|
||||
@implementation HIDBLEDevice
|
||||
|
||||
- (id)init
|
||||
{
|
||||
if ( self = [super init] )
|
||||
{
|
||||
self.bleSteamController = nil;
|
||||
self.bleCharacteristicInput = nil;
|
||||
self.bleCharacteristicReport = nil;
|
||||
_connected = NO;
|
||||
_ready = NO;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)initWithPeripheral:(CBPeripheral *)peripheral
|
||||
{
|
||||
if ( self = [super init] )
|
||||
{
|
||||
_connected = NO;
|
||||
_ready = NO;
|
||||
self.bleSteamController = peripheral;
|
||||
if ( peripheral )
|
||||
{
|
||||
peripheral.delegate = self;
|
||||
}
|
||||
self.bleCharacteristicInput = nil;
|
||||
self.bleCharacteristicReport = nil;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setConnected:(bool)connected
|
||||
{
|
||||
_connected = connected;
|
||||
if ( _connected )
|
||||
{
|
||||
[_bleSteamController discoverServices:nil];
|
||||
}
|
||||
else
|
||||
{
|
||||
NSLog( @"Disconnected" );
|
||||
}
|
||||
}
|
||||
|
||||
- (size_t)read_input_report:(uint8_t *)dst
|
||||
{
|
||||
if ( _inputReports.read( dst+1 ) )
|
||||
{
|
||||
*dst = 0x03;
|
||||
return 20;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
- (int)send_report:(const uint8_t *)data length:(size_t)length
|
||||
{
|
||||
[_bleSteamController writeValue:[NSData dataWithBytes:data length:length] forCharacteristic:_bleCharacteristicReport type:CBCharacteristicWriteWithResponse];
|
||||
return (int)length;
|
||||
}
|
||||
|
||||
- (int)send_feature_report:(hidFeatureReport *)report
|
||||
{
|
||||
#if FEATURE_REPORT_LOGGING
|
||||
uint8_t *reportBytes = (uint8_t *)report;
|
||||
|
||||
NSLog( @"HIDBLE:send_feature_report (%02zu/19) [%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x]", report->segment.size(),
|
||||
reportBytes[1], reportBytes[2], reportBytes[3], reportBytes[4], reportBytes[5], reportBytes[6],
|
||||
reportBytes[7], reportBytes[8], reportBytes[9], reportBytes[10], reportBytes[11], reportBytes[12],
|
||||
reportBytes[13], reportBytes[14], reportBytes[15], reportBytes[16], reportBytes[17], reportBytes[18],
|
||||
reportBytes[19] );
|
||||
#endif
|
||||
|
||||
int sendSize = (int)report->segment.size();
|
||||
if ( sendSize > 20 )
|
||||
sendSize = 20;
|
||||
|
||||
#if 1
|
||||
// fire-and-forget - we are going to not wait for the response here because all Steam Controller BLE send_feature_report's are ignored,
|
||||
// except errors.
|
||||
[_bleSteamController writeValue:[NSData dataWithBytes:&report->segment length:sendSize] forCharacteristic:_bleCharacteristicReport type:CBCharacteristicWriteWithResponse];
|
||||
|
||||
// pretend we received a result anybody cares about
|
||||
return 19;
|
||||
|
||||
#else
|
||||
// this is technically the correct send_feature_report logic if you want to make sure it gets through and is
|
||||
// acknowledged or errors out
|
||||
_waitStateForWriteFeatureReport = BLEDeviceWaitState::Waiting;
|
||||
[_bleSteamController writeValue:[NSData dataWithBytes:&report->segment length:sendSize
|
||||
] forCharacteristic:_bleCharacteristicReport type:CBCharacteristicWriteWithResponse];
|
||||
|
||||
while ( _waitStateForWriteFeatureReport == BLEDeviceWaitState::Waiting )
|
||||
{
|
||||
process_pending_events();
|
||||
}
|
||||
|
||||
if ( _waitStateForWriteFeatureReport == BLEDeviceWaitState::Error )
|
||||
{
|
||||
_waitStateForWriteFeatureReport = BLEDeviceWaitState::None;
|
||||
return -1;
|
||||
}
|
||||
|
||||
_waitStateForWriteFeatureReport = BLEDeviceWaitState::None;
|
||||
return 19;
|
||||
#endif
|
||||
}
|
||||
|
||||
- (int)get_feature_report:(uint8_t)feature into:(uint8_t *)buffer
|
||||
{
|
||||
_waitStateForReadFeatureReport = BLEDeviceWaitState::Waiting;
|
||||
[_bleSteamController readValueForCharacteristic:_bleCharacteristicReport];
|
||||
|
||||
while ( _waitStateForReadFeatureReport == BLEDeviceWaitState::Waiting )
|
||||
process_pending_events();
|
||||
|
||||
if ( _waitStateForReadFeatureReport == BLEDeviceWaitState::Error )
|
||||
{
|
||||
_waitStateForReadFeatureReport = BLEDeviceWaitState::None;
|
||||
return -1;
|
||||
}
|
||||
|
||||
memcpy( buffer, _featureReport, sizeof(_featureReport) );
|
||||
|
||||
_waitStateForReadFeatureReport = BLEDeviceWaitState::None;
|
||||
|
||||
#if FEATURE_REPORT_LOGGING
|
||||
NSLog( @"HIDBLE:get_feature_report (19) [%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x]",
|
||||
buffer[1], buffer[2], buffer[3], buffer[4], buffer[5], buffer[6],
|
||||
buffer[7], buffer[8], buffer[9], buffer[10], buffer[11], buffer[12],
|
||||
buffer[13], buffer[14], buffer[15], buffer[16], buffer[17], buffer[18],
|
||||
buffer[19] );
|
||||
#endif
|
||||
|
||||
return 19;
|
||||
}
|
||||
|
||||
#pragma mark CBPeripheralDelegate Implementation
|
||||
|
||||
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error
|
||||
{
|
||||
for (CBService *service in peripheral.services)
|
||||
{
|
||||
NSLog( @"Found Service: %@", service );
|
||||
if ( [service.UUID isEqual:[CBUUID UUIDWithString:VALVE_SERVICE]] )
|
||||
{
|
||||
[peripheral discoverCharacteristics:nil forService:service];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverDescriptorsForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
|
||||
{
|
||||
// nothing yet needed here, enable for logging
|
||||
if ( /* DISABLES CODE */ (0) )
|
||||
{
|
||||
for ( CBDescriptor *descriptor in characteristic.descriptors )
|
||||
{
|
||||
NSLog( @" - Descriptor '%@'", descriptor );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
|
||||
{
|
||||
if ([service.UUID isEqual:[CBUUID UUIDWithString:VALVE_SERVICE]])
|
||||
{
|
||||
for (CBCharacteristic *aChar in service.characteristics)
|
||||
{
|
||||
NSLog( @"Found Characteristic %@", aChar );
|
||||
|
||||
if ( [aChar.UUID isEqual:[CBUUID UUIDWithString:VALVE_INPUT_CHAR]] )
|
||||
{
|
||||
self.bleCharacteristicInput = aChar;
|
||||
}
|
||||
else if ( [aChar.UUID isEqual:[CBUUID UUIDWithString:VALVE_REPORT_CHAR]] )
|
||||
{
|
||||
self.bleCharacteristicReport = aChar;
|
||||
[self.bleSteamController discoverDescriptorsForCharacteristic: aChar];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
|
||||
{
|
||||
static uint64_t s_ticksLastOverflowReport = 0;
|
||||
|
||||
// receiving an input report is the final indicator that the user accepted a pairing
|
||||
// request and that we successfully established notification. CoreBluetooth has no
|
||||
// notification of the pairing acknowledgement, which is a bad oversight.
|
||||
if ( self.ready == NO )
|
||||
{
|
||||
self.ready = YES;
|
||||
HIDBLEManager.sharedInstance.nPendingPairs -= 1;
|
||||
}
|
||||
|
||||
if ( [characteristic.UUID isEqual:_bleCharacteristicInput.UUID] )
|
||||
{
|
||||
NSData *data = [characteristic value];
|
||||
if ( data.length != 19 )
|
||||
{
|
||||
NSLog( @"HIDBLE: incoming data is %lu bytes should be exactly 19", (unsigned long)data.length );
|
||||
}
|
||||
if ( !_inputReports.write( (const uint8_t *)data.bytes ) )
|
||||
{
|
||||
uint64_t ticksNow = mach_approximate_time();
|
||||
if ( ticksNow - s_ticksLastOverflowReport > (5ull * NSEC_PER_SEC / 10) )
|
||||
{
|
||||
NSLog( @"HIDBLE: input report buffer overflow" );
|
||||
s_ticksLastOverflowReport = ticksNow;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ( [characteristic.UUID isEqual:_bleCharacteristicReport.UUID] )
|
||||
{
|
||||
memset( _featureReport, 0, sizeof(_featureReport) );
|
||||
|
||||
if ( error != nil )
|
||||
{
|
||||
NSLog( @"HIDBLE: get_feature_report error: %@", error );
|
||||
_waitStateForReadFeatureReport = BLEDeviceWaitState::Error;
|
||||
}
|
||||
else
|
||||
{
|
||||
NSData *data = [characteristic value];
|
||||
if ( data.length != 20 )
|
||||
{
|
||||
NSLog( @"HIDBLE: incoming data is %lu bytes should be exactly 20", (unsigned long)data.length );
|
||||
}
|
||||
memcpy( _featureReport, data.bytes, MIN( data.length, sizeof(_featureReport) ) );
|
||||
_waitStateForReadFeatureReport = BLEDeviceWaitState::Complete;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
|
||||
{
|
||||
if ( [characteristic.UUID isEqual:[CBUUID UUIDWithString:VALVE_REPORT_CHAR]] )
|
||||
{
|
||||
if ( error != nil )
|
||||
{
|
||||
NSLog( @"HIDBLE: write_feature_report error: %@", error );
|
||||
_waitStateForWriteFeatureReport = BLEDeviceWaitState::Error;
|
||||
}
|
||||
else
|
||||
{
|
||||
_waitStateForWriteFeatureReport = BLEDeviceWaitState::Complete;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
|
||||
{
|
||||
NSLog( @"didUpdateNotifcationStateForCharacteristic %@ (%@)", characteristic, error );
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
#pragma mark hid_api implementation
|
||||
|
||||
struct hid_device_ {
|
||||
HIDBLEDevice *device_handle;
|
||||
int blocking;
|
||||
hid_device *next;
|
||||
};
|
||||
|
||||
int HID_API_EXPORT HID_API_CALL hid_init(void)
|
||||
{
|
||||
return ( HIDBLEManager.sharedInstance == nil ) ? -1 : 0;
|
||||
}
|
||||
|
||||
int HID_API_EXPORT HID_API_CALL hid_exit(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
void HID_API_EXPORT HID_API_CALL hid_ble_scan( bool bStart )
|
||||
{
|
||||
HIDBLEManager *bleManager = HIDBLEManager.sharedInstance;
|
||||
if ( bStart )
|
||||
{
|
||||
[bleManager startScan:0];
|
||||
}
|
||||
else
|
||||
{
|
||||
[bleManager stopScan];
|
||||
}
|
||||
}
|
||||
|
||||
hid_device * HID_API_EXPORT hid_open_path( const char *path, int bExclusive /* = false */ )
|
||||
{
|
||||
hid_device *result = NULL;
|
||||
NSString *nssPath = [NSString stringWithUTF8String:path];
|
||||
HIDBLEManager *bleManager = HIDBLEManager.sharedInstance;
|
||||
NSEnumerator<HIDBLEDevice *> *devices = [bleManager.deviceMap objectEnumerator];
|
||||
|
||||
for ( HIDBLEDevice *device in devices )
|
||||
{
|
||||
// we have the device but it hasn't found its service or characteristics until it is connected
|
||||
if ( !device.ready || !device.connected || !device.bleCharacteristicInput )
|
||||
continue;
|
||||
|
||||
if ( [device.bleSteamController.identifier.UUIDString isEqualToString:nssPath] )
|
||||
{
|
||||
result = (hid_device *)malloc( sizeof( hid_device ) );
|
||||
memset( result, 0, sizeof( hid_device ) );
|
||||
result->device_handle = device;
|
||||
result->blocking = NO;
|
||||
// enable reporting input events on the characteristic
|
||||
[device.bleSteamController setNotifyValue:YES forCharacteristic:device.bleCharacteristicInput];
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void HID_API_EXPORT hid_free_enumeration(struct hid_device_info *devs)
|
||||
{
|
||||
/* This function is identical to the Linux version. Platform independent. */
|
||||
struct hid_device_info *d = devs;
|
||||
while (d) {
|
||||
struct hid_device_info *next = d->next;
|
||||
free(d->path);
|
||||
free(d->serial_number);
|
||||
free(d->manufacturer_string);
|
||||
free(d->product_string);
|
||||
free(d);
|
||||
d = next;
|
||||
}
|
||||
}
|
||||
|
||||
int HID_API_EXPORT hid_set_nonblocking(hid_device *dev, int nonblock)
|
||||
{
|
||||
/* All Nonblocking operation is handled by the library. */
|
||||
dev->blocking = !nonblock;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, unsigned short product_id)
|
||||
{ @autoreleasepool {
|
||||
struct hid_device_info *root = NULL;
|
||||
|
||||
if ( ( vendor_id == 0 && product_id == 0 ) ||
|
||||
( vendor_id == VALVE_USB_VID && product_id == D0G_BLE2_PID ) )
|
||||
{
|
||||
HIDBLEManager *bleManager = HIDBLEManager.sharedInstance;
|
||||
[bleManager updateConnectedSteamControllers:false];
|
||||
NSEnumerator<HIDBLEDevice *> *devices = [bleManager.deviceMap objectEnumerator];
|
||||
for ( HIDBLEDevice *device in devices )
|
||||
{
|
||||
// there are several brief windows in connecting to an already paired device and
|
||||
// one long window waiting for users to confirm pairing where we don't want
|
||||
// to consider a device ready - if we hand it back to SDL or another
|
||||
// Steam Controller consumer, their additional SC setup work will fail
|
||||
// in unusual/silent ways and we can actually corrupt the BLE stack for
|
||||
// the entire system and kill the appletv remote's Menu button (!)
|
||||
if ( device.bleSteamController.state != CBPeripheralStateConnected ||
|
||||
device.connected == NO || device.ready == NO )
|
||||
{
|
||||
if ( device.ready == NO && device.bleCharacteristicInput != nil )
|
||||
{
|
||||
// attempt to register for input reports. this call will silently fail
|
||||
// until the pairing finalizes with user acceptance. oh, apple.
|
||||
[device.bleSteamController setNotifyValue:YES forCharacteristic:device.bleCharacteristicInput];
|
||||
}
|
||||
continue;
|
||||
}
|
||||
hid_device_info *device_info = (hid_device_info *)malloc( sizeof(hid_device_info) );
|
||||
memset( device_info, 0, sizeof(hid_device_info) );
|
||||
device_info->next = root;
|
||||
root = device_info;
|
||||
device_info->path = strdup( device.bleSteamController.identifier.UUIDString.UTF8String );
|
||||
device_info->vendor_id = VALVE_USB_VID;
|
||||
device_info->product_id = D0G_BLE2_PID;
|
||||
device_info->product_string = wcsdup( L"Steam Controller" );
|
||||
device_info->manufacturer_string = wcsdup( L"Valve Corporation" );
|
||||
}
|
||||
}
|
||||
return root;
|
||||
}}
|
||||
|
||||
int HID_API_EXPORT_CALL hid_get_manufacturer_string(hid_device *dev, wchar_t *string, size_t maxlen)
|
||||
{
|
||||
static wchar_t s_wszManufacturer[] = L"Valve Corporation";
|
||||
wcsncpy( string, s_wszManufacturer, sizeof(s_wszManufacturer)/sizeof(s_wszManufacturer[0]) );
|
||||
return 0;
|
||||
}
|
||||
|
||||
int HID_API_EXPORT_CALL hid_get_product_string(hid_device *dev, wchar_t *string, size_t maxlen)
|
||||
{
|
||||
static wchar_t s_wszProduct[] = L"Steam Controller";
|
||||
wcsncpy( string, s_wszProduct, sizeof(s_wszProduct)/sizeof(s_wszProduct[0]) );
|
||||
return 0;
|
||||
}
|
||||
|
||||
int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen)
|
||||
{
|
||||
static wchar_t s_wszSerial[] = L"12345";
|
||||
wcsncpy( string, s_wszSerial, sizeof(s_wszSerial)/sizeof(s_wszSerial[0]) );
|
||||
return 0;
|
||||
}
|
||||
|
||||
int HID_API_EXPORT hid_write(hid_device *dev, const unsigned char *data, size_t length)
|
||||
{
|
||||
if ( !dev->device_handle.connected )
|
||||
return -1;
|
||||
|
||||
return [dev->device_handle send_report:data length:length];
|
||||
}
|
||||
|
||||
void HID_API_EXPORT hid_close(hid_device *dev)
|
||||
{
|
||||
// disable reporting input events on the characteristic
|
||||
if ( dev->device_handle.connected ) {
|
||||
[dev->device_handle.bleSteamController setNotifyValue:NO forCharacteristic:dev->device_handle.bleCharacteristicInput];
|
||||
}
|
||||
|
||||
free( dev );
|
||||
}
|
||||
|
||||
int HID_API_EXPORT hid_send_feature_report(hid_device *dev, const unsigned char *data, size_t length)
|
||||
{
|
||||
if ( !dev->device_handle.connected )
|
||||
return -1;
|
||||
|
||||
return [dev->device_handle send_feature_report:(hidFeatureReport *)(void *)data];
|
||||
}
|
||||
|
||||
int HID_API_EXPORT hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length)
|
||||
{
|
||||
if ( !dev->device_handle.connected )
|
||||
return -1;
|
||||
|
||||
size_t written = [dev->device_handle get_feature_report:data[0] into:data];
|
||||
|
||||
return written == length-1 ? (int)length : (int)written;
|
||||
}
|
||||
|
||||
int HID_API_EXPORT hid_read(hid_device *dev, unsigned char *data, size_t length)
|
||||
{
|
||||
if ( !dev->device_handle.connected )
|
||||
return -1;
|
||||
|
||||
return hid_read_timeout(dev, data, length, 0);
|
||||
}
|
||||
|
||||
int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t length, int milliseconds)
|
||||
{
|
||||
if ( !dev->device_handle.connected )
|
||||
return -1;
|
||||
|
||||
if ( milliseconds != 0 )
|
||||
{
|
||||
NSLog( @"hid_read_timeout with non-zero wait" );
|
||||
}
|
||||
int result = (int)[dev->device_handle read_input_report:data];
|
||||
#if FEATURE_REPORT_LOGGING
|
||||
NSLog( @"HIDBLE:hid_read_timeout (%d) [%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x]", result,
|
||||
data[1], data[2], data[3], data[4], data[5], data[6],
|
||||
data[7], data[8], data[9], data[10], data[11], data[12],
|
||||
data[13], data[14], data[15], data[16], data[17], data[18],
|
||||
data[19] );
|
||||
#endif
|
||||
return result;
|
||||
}
|
Reference in New Issue
Block a user