mirror of
https://github.com/moonlight-stream/moonlight-ios.git
synced 2025-07-03 00:06:31 +00:00
1197 lines
50 KiB
Objective-C
1197 lines
50 KiB
Objective-C
//
|
|
// ControllerSupport.m
|
|
// Moonlight
|
|
//
|
|
// Created by Cameron Gutman on 10/20/14.
|
|
// Copyright (c) 2014 Moonlight Stream. All rights reserved.
|
|
//
|
|
|
|
#import "ControllerSupport.h"
|
|
#import "Controller.h"
|
|
|
|
#import "OnScreenControls.h"
|
|
|
|
#import "DataManager.h"
|
|
#include "Limelight.h"
|
|
|
|
@import GameController;
|
|
@import AudioToolbox;
|
|
|
|
static const double MOUSE_SPEED_DIVISOR = 1.25;
|
|
|
|
@implementation ControllerSupport {
|
|
id _controllerConnectObserver;
|
|
id _controllerDisconnectObserver;
|
|
id _mouseConnectObserver;
|
|
id _mouseDisconnectObserver;
|
|
id _keyboardConnectObserver;
|
|
id _keyboardDisconnectObserver;
|
|
|
|
NSLock *_controllerStreamLock;
|
|
NSMutableDictionary *_controllers;
|
|
id<ControllerSupportDelegate> _delegate;
|
|
|
|
float accumulatedDeltaX;
|
|
float accumulatedDeltaY;
|
|
float accumulatedScrollX;
|
|
float accumulatedScrollY;
|
|
|
|
OnScreenControls *_osc;
|
|
|
|
// This controller object is shared between on-screen controls
|
|
// and player 0
|
|
Controller *_player0osc;
|
|
|
|
#define EMULATING_SELECT 0x1
|
|
#define EMULATING_SPECIAL 0x2
|
|
|
|
bool _oscEnabled;
|
|
char _controllerNumbers;
|
|
bool _multiController;
|
|
bool _swapABXYButtons;
|
|
}
|
|
|
|
// UPDATE_BUTTON_FLAG(controller, flag, pressed)
|
|
#define UPDATE_BUTTON_FLAG(controller, x, y) \
|
|
((y) ? [self setButtonFlag:controller flags:x] : [self clearButtonFlag:controller flags:x])
|
|
|
|
-(void) rumble:(unsigned short)controllerNumber lowFreqMotor:(unsigned short)lowFreqMotor highFreqMotor:(unsigned short)highFreqMotor
|
|
{
|
|
Controller* controller = [_controllers objectForKey:[NSNumber numberWithInteger:controllerNumber]];
|
|
if (controller == nil && controllerNumber == 0 && _oscEnabled) {
|
|
// No physical controller, but we have on-screen controls
|
|
controller = _player0osc;
|
|
}
|
|
if (controller == nil) {
|
|
// No connected controller for this player
|
|
return;
|
|
}
|
|
|
|
[controller.lowFreqMotor setMotorAmplitude:lowFreqMotor];
|
|
[controller.highFreqMotor setMotorAmplitude:highFreqMotor];
|
|
}
|
|
|
|
-(void) rumbleTriggers:(uint16_t)controllerNumber leftTrigger:(uint16_t)leftTrigger rightTrigger:(uint16_t)rightTrigger
|
|
{
|
|
Controller* controller = [_controllers objectForKey:[NSNumber numberWithInteger:controllerNumber]];
|
|
if (controller == nil && controllerNumber == 0 && _oscEnabled) {
|
|
// No physical controller, but we have on-screen controls
|
|
controller = _player0osc;
|
|
}
|
|
if (controller == nil) {
|
|
// No connected controller for this player
|
|
return;
|
|
}
|
|
|
|
[controller.leftTriggerMotor setMotorAmplitude:leftTrigger];
|
|
[controller.rightTriggerMotor setMotorAmplitude:rightTrigger];
|
|
}
|
|
|
|
- (void) setMotionEventState:(uint16_t)controllerNumber motionType:(uint8_t)motionType reportRateHz:(uint16_t)reportRateHz
|
|
{
|
|
if (@available(iOS 14.0, tvOS 14.0, *)) {
|
|
Controller* controller = [_controllers objectForKey:[NSNumber numberWithInteger:controllerNumber]];
|
|
if (controller == nil) {
|
|
// No connected controller for this player
|
|
return;
|
|
}
|
|
|
|
if (controller.gamepad.motion == nil) {
|
|
// No motion supported for this controller
|
|
return;
|
|
}
|
|
|
|
switch (motionType) {
|
|
case LI_MOTION_TYPE_ACCEL:
|
|
[controller.accelTimer invalidate];
|
|
controller.accelTimer = nil;
|
|
|
|
if (reportRateHz && controller.gamepad.motion.hasGravityAndUserAcceleration) {
|
|
// Reset the last motion sample
|
|
GCAcceleration emptyAccelSample = {};
|
|
controller.lastAccelSample = emptyAccelSample;
|
|
|
|
dispatch_sync(dispatch_get_main_queue(), ^{
|
|
controller.accelTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 / reportRateHz repeats:YES block:^(NSTimer *timer) {
|
|
// Don't send duplicate samples
|
|
GCAcceleration lastAccelSample = controller.lastAccelSample;
|
|
GCAcceleration accelSample = controller.gamepad.motion.acceleration;
|
|
if (memcmp(&accelSample, &lastAccelSample, sizeof(accelSample)) == 0) {
|
|
return;
|
|
}
|
|
controller.lastAccelSample = accelSample;
|
|
|
|
// Convert g to m/s^2
|
|
LiSendControllerMotionEvent((uint8_t)controllerNumber,
|
|
LI_MOTION_TYPE_ACCEL,
|
|
accelSample.x * 9.80665f,
|
|
accelSample.y * 9.80665f,
|
|
accelSample.z * 9.80665f);
|
|
}];
|
|
});
|
|
}
|
|
break;
|
|
|
|
case LI_MOTION_TYPE_GYRO:
|
|
[controller.gyroTimer invalidate];
|
|
controller.gyroTimer = nil;
|
|
|
|
if (reportRateHz && controller.gamepad.motion.hasRotationRate) {
|
|
// Reset the last motion sample
|
|
GCRotationRate emptyGyroSample = {};
|
|
controller.lastGyroSample = emptyGyroSample;
|
|
|
|
dispatch_sync(dispatch_get_main_queue(), ^{
|
|
controller.gyroTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 / reportRateHz repeats:YES block:^(NSTimer *timer) {
|
|
// Don't send duplicate samples
|
|
GCRotationRate lastGyroSample = controller.lastGyroSample;
|
|
GCRotationRate gyroSample = controller.gamepad.motion.rotationRate;
|
|
if (memcmp(&gyroSample, &lastGyroSample, sizeof(gyroSample)) == 0) {
|
|
return;
|
|
}
|
|
controller.lastGyroSample = gyroSample;
|
|
|
|
// Convert rad/s to deg/s
|
|
LiSendControllerMotionEvent((uint8_t)controllerNumber,
|
|
LI_MOTION_TYPE_GYRO,
|
|
gyroSample.x * 57.2957795f,
|
|
gyroSample.y * 57.2957795f,
|
|
gyroSample.z * 57.2957795f);
|
|
}];
|
|
});
|
|
}
|
|
break;
|
|
}
|
|
|
|
// Set the motion sensor state if they require manual activation
|
|
if (controller.gamepad.motion.sensorsRequireManualActivation) {
|
|
if (controller.gyroTimer || controller.accelTimer) {
|
|
controller.gamepad.motion.sensorsActive = YES;
|
|
}
|
|
else {
|
|
controller.gamepad.motion.sensorsActive = NO;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
-(void) updateLeftStick:(Controller*)controller x:(short)x y:(short)y
|
|
{
|
|
@synchronized(controller) {
|
|
controller.lastLeftStickX = x;
|
|
controller.lastLeftStickY = y;
|
|
}
|
|
}
|
|
|
|
-(void) updateRightStick:(Controller*)controller x:(short)x y:(short)y
|
|
{
|
|
@synchronized(controller) {
|
|
controller.lastRightStickX = x;
|
|
controller.lastRightStickY = y;
|
|
}
|
|
}
|
|
|
|
-(void) updateLeftTrigger:(Controller*)controller left:(unsigned char)left
|
|
{
|
|
@synchronized(controller) {
|
|
controller.lastLeftTrigger = left;
|
|
}
|
|
}
|
|
|
|
-(void) updateRightTrigger:(Controller*)controller right:(unsigned char)right
|
|
{
|
|
@synchronized(controller) {
|
|
controller.lastRightTrigger = right;
|
|
}
|
|
}
|
|
|
|
-(void) updateTriggers:(Controller*) controller left:(unsigned char)left right:(unsigned char)right
|
|
{
|
|
@synchronized(controller) {
|
|
controller.lastLeftTrigger = left;
|
|
controller.lastRightTrigger = right;
|
|
}
|
|
}
|
|
|
|
-(void) handleSpecialCombosReleased:(Controller*)controller releasedButtons:(int)releasedButtons
|
|
{
|
|
if ((controller.emulatingButtonFlags & EMULATING_SELECT) && (releasedButtons & (LB_FLAG | PLAY_FLAG))) {
|
|
controller.lastButtonFlags &= ~BACK_FLAG;
|
|
controller.emulatingButtonFlags &= ~EMULATING_SELECT;
|
|
}
|
|
|
|
if (controller.emulatingButtonFlags & EMULATING_SPECIAL) {
|
|
// If Select is emulated, we use RB+Start to emulate special, otherwise we use Start+Select
|
|
if (controller.supportedEmulationFlags & EMULATING_SELECT) {
|
|
if (releasedButtons & (RB_FLAG | PLAY_FLAG)) {
|
|
controller.lastButtonFlags &= ~SPECIAL_FLAG;
|
|
controller.emulatingButtonFlags &= ~EMULATING_SPECIAL;
|
|
}
|
|
}
|
|
else {
|
|
if (releasedButtons & (BACK_FLAG | PLAY_FLAG)) {
|
|
controller.lastButtonFlags &= ~SPECIAL_FLAG;
|
|
controller.emulatingButtonFlags &= ~EMULATING_SPECIAL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
-(void) handleSpecialCombosPressed:(Controller*)controller pressedButtons:(int)pressedButtons
|
|
{
|
|
// Special button combos for select and special
|
|
if (controller.lastButtonFlags & PLAY_FLAG) {
|
|
// If LB and start are down, trigger select
|
|
if (controller.lastButtonFlags & LB_FLAG) {
|
|
if (controller.supportedEmulationFlags & EMULATING_SELECT) {
|
|
controller.lastButtonFlags |= BACK_FLAG;
|
|
controller.lastButtonFlags &= ~(pressedButtons & (PLAY_FLAG | LB_FLAG));
|
|
controller.emulatingButtonFlags |= EMULATING_SELECT;
|
|
}
|
|
}
|
|
else if (controller.supportedEmulationFlags & EMULATING_SPECIAL) {
|
|
// If Select is emulated too, use RB+Start to emulate special
|
|
if (controller.supportedEmulationFlags & EMULATING_SELECT) {
|
|
if (controller.lastButtonFlags & RB_FLAG) {
|
|
controller.lastButtonFlags |= SPECIAL_FLAG;
|
|
controller.lastButtonFlags &= ~(pressedButtons & (PLAY_FLAG | RB_FLAG));
|
|
controller.emulatingButtonFlags |= EMULATING_SPECIAL;
|
|
}
|
|
}
|
|
else {
|
|
// If Select is physical, use Start+Select to emulate special
|
|
if (controller.lastButtonFlags & BACK_FLAG) {
|
|
controller.lastButtonFlags |= SPECIAL_FLAG;
|
|
controller.lastButtonFlags &= ~(pressedButtons & (PLAY_FLAG | BACK_FLAG));
|
|
controller.emulatingButtonFlags |= EMULATING_SPECIAL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
-(void) updateButtonFlags:(Controller*)controller flags:(int)flags
|
|
{
|
|
@synchronized(controller) {
|
|
controller.lastButtonFlags = flags;
|
|
|
|
// This must be called before handleSpecialCombosPressed
|
|
// because we clear the original button flags there
|
|
int releasedButtons = (controller.lastButtonFlags ^ flags) & ~flags;
|
|
int pressedButtons = (controller.lastButtonFlags ^ flags) & flags;
|
|
|
|
[self handleSpecialCombosReleased:controller releasedButtons:releasedButtons];
|
|
|
|
[self handleSpecialCombosPressed:controller pressedButtons:pressedButtons];
|
|
}
|
|
}
|
|
|
|
-(void) setButtonFlag:(Controller*)controller flags:(int)flags
|
|
{
|
|
@synchronized(controller) {
|
|
controller.lastButtonFlags |= flags;
|
|
[self handleSpecialCombosPressed:controller pressedButtons:flags];
|
|
}
|
|
}
|
|
|
|
-(void) clearButtonFlag:(Controller*)controller flags:(int)flags
|
|
{
|
|
@synchronized(controller) {
|
|
controller.lastButtonFlags &= ~flags;
|
|
[self handleSpecialCombosReleased:controller releasedButtons:flags];
|
|
}
|
|
}
|
|
|
|
-(uint16_t) getActiveGamepadMask
|
|
{
|
|
return (_multiController ? _controllerNumbers : 1) | (_oscEnabled ? 1 : 0);
|
|
}
|
|
|
|
-(void) updateFinished:(Controller*)controller
|
|
{
|
|
BOOL exitRequested = NO;
|
|
|
|
[_controllerStreamLock lock];
|
|
@synchronized(controller) {
|
|
// Handle Start+Select+L1+R1 gamepad quit combo
|
|
if (controller.lastButtonFlags == (PLAY_FLAG | BACK_FLAG | LB_FLAG | RB_FLAG)) {
|
|
controller.lastButtonFlags = 0;
|
|
exitRequested = YES;
|
|
}
|
|
|
|
// Report the controller arrival to the host if we haven't done so yet
|
|
if (!controller.reportedArrival && controller.gamepad) {
|
|
[self reportControllerArrival:controller.gamepad];
|
|
controller.reportedArrival = YES;
|
|
}
|
|
|
|
// Player 1 is always present for OSC
|
|
LiSendMultiControllerEvent(_multiController ? controller.playerIndex : 0, [self getActiveGamepadMask],
|
|
controller.lastButtonFlags, controller.lastLeftTrigger, controller.lastRightTrigger, controller.lastLeftStickX, controller.lastLeftStickY, controller.lastRightStickX, controller.lastRightStickY);
|
|
}
|
|
[_controllerStreamLock unlock];
|
|
|
|
if (exitRequested) {
|
|
// Invoke the delegate callback on the main thread
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
[self->_delegate streamExitRequested];
|
|
});
|
|
}
|
|
}
|
|
|
|
+(BOOL) hasKeyboardOrMouse {
|
|
if (@available(iOS 14.0, tvOS 14.0, *)) {
|
|
return GCMouse.mice.count > 0 || GCKeyboard.coalescedKeyboard != nil;
|
|
}
|
|
else {
|
|
return NO;
|
|
}
|
|
}
|
|
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
|
|
|
-(void) unregisterControllerCallbacks:(GCController*) controller
|
|
{
|
|
if (controller != NULL) {
|
|
controller.controllerPausedHandler = NULL;
|
|
|
|
if (controller.extendedGamepad != NULL) {
|
|
// Re-enable system gestures on the gamepad buttons now
|
|
if (@available(iOS 14.0, tvOS 14.0, *)) {
|
|
for (GCControllerElement* element in controller.extendedGamepad.allElements) {
|
|
element.preferredSystemGestureState = GCSystemGestureStateEnabled;
|
|
}
|
|
}
|
|
|
|
controller.extendedGamepad.valueChangedHandler = NULL;
|
|
}
|
|
else if (controller.gamepad != NULL) {
|
|
controller.gamepad.valueChangedHandler = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
-(void) initializeControllerHaptics:(Controller*) controller
|
|
{
|
|
controller.lowFreqMotor = [HapticContext createContextForLowFreqMotor:controller.gamepad];
|
|
controller.highFreqMotor = [HapticContext createContextForHighFreqMotor:controller.gamepad];
|
|
controller.leftTriggerMotor = [HapticContext createContextForLeftTrigger:controller.gamepad];
|
|
controller.rightTriggerMotor = [HapticContext createContextForRightTrigger:controller.gamepad];
|
|
}
|
|
|
|
-(void) cleanupControllerHaptics:(Controller*) controller
|
|
{
|
|
[controller.lowFreqMotor cleanup];
|
|
[controller.highFreqMotor cleanup];
|
|
[controller.leftTriggerMotor cleanup];
|
|
[controller.rightTriggerMotor cleanup];
|
|
}
|
|
|
|
-(void) cleanupControllerMotion:(Controller*) controller
|
|
{
|
|
if (@available(iOS 14.0, tvOS 14.0, *)) {
|
|
// Stop sensor sampling timers
|
|
[controller.gyroTimer invalidate];
|
|
[controller.accelTimer invalidate];
|
|
|
|
// Disable motion sensors if they require manual activation
|
|
if (controller.gamepad && controller.gamepad.motion && controller.gamepad.motion.sensorsRequireManualActivation) {
|
|
controller.gamepad.motion.sensorsActive = NO;
|
|
}
|
|
}
|
|
}
|
|
|
|
-(void) reportControllerArrival:(GCController*) controller
|
|
{
|
|
if (controller == nil || controller.extendedGamepad == nil) {
|
|
return;
|
|
}
|
|
|
|
uint8_t type = LI_CTYPE_UNKNOWN;
|
|
uint16_t capabilities = 0;
|
|
uint32_t supportedButtonFlags = 0;
|
|
|
|
// Start is always present
|
|
supportedButtonFlags |= PLAY_FLAG;
|
|
|
|
// Detect buttons present in the GCExtendedGamepad profile
|
|
if (controller.extendedGamepad.dpad) {
|
|
supportedButtonFlags |= UP_FLAG | DOWN_FLAG | LEFT_FLAG | RIGHT_FLAG;
|
|
}
|
|
if (controller.extendedGamepad.leftShoulder) {
|
|
supportedButtonFlags |= LB_FLAG;
|
|
}
|
|
if (controller.extendedGamepad.rightShoulder) {
|
|
supportedButtonFlags |= RB_FLAG;
|
|
}
|
|
if (@available(iOS 13.0, tvOS 13.0, *)) {
|
|
if (controller.extendedGamepad.buttonOptions) {
|
|
supportedButtonFlags |= BACK_FLAG;
|
|
}
|
|
}
|
|
if (@available(iOS 14.0, tvOS 14.0, *)) {
|
|
if (controller.extendedGamepad.buttonHome) {
|
|
supportedButtonFlags |= SPECIAL_FLAG;
|
|
}
|
|
}
|
|
if (controller.extendedGamepad.buttonA) {
|
|
supportedButtonFlags |= A_FLAG;
|
|
}
|
|
if (controller.extendedGamepad.buttonB) {
|
|
supportedButtonFlags |= B_FLAG;
|
|
}
|
|
if (controller.extendedGamepad.buttonX) {
|
|
supportedButtonFlags |= X_FLAG;
|
|
}
|
|
if (controller.extendedGamepad.buttonY) {
|
|
supportedButtonFlags |= Y_FLAG;
|
|
}
|
|
if (@available(iOS 12.1, tvOS 12.1, *)) {
|
|
if (controller.extendedGamepad.leftThumbstickButton) {
|
|
supportedButtonFlags |= LS_CLK_FLAG;
|
|
}
|
|
if (controller.extendedGamepad.rightThumbstickButton) {
|
|
supportedButtonFlags |= RS_CLK_FLAG;
|
|
}
|
|
}
|
|
|
|
if (@available(iOS 14.0, tvOS 14.0, *)) {
|
|
if ([controller.extendedGamepad isKindOfClass:[GCXboxGamepad class]]) {
|
|
GCXboxGamepad* xboxGamepad = (GCXboxGamepad*)controller.extendedGamepad;
|
|
|
|
if (xboxGamepad.paddleButton1) {
|
|
supportedButtonFlags |= PADDLE1_FLAG;
|
|
}
|
|
if (xboxGamepad.paddleButton2) {
|
|
supportedButtonFlags |= PADDLE2_FLAG;
|
|
}
|
|
if (xboxGamepad.paddleButton3) {
|
|
supportedButtonFlags |= PADDLE3_FLAG;
|
|
}
|
|
if (xboxGamepad.paddleButton4) {
|
|
supportedButtonFlags |= PADDLE4_FLAG;
|
|
}
|
|
|
|
if (@available(iOS 15.0, tvOS 15.0, *)) {
|
|
if (xboxGamepad.buttonShare) {
|
|
supportedButtonFlags |= MISC_FLAG;
|
|
}
|
|
}
|
|
|
|
type = LI_CTYPE_XBOX;
|
|
}
|
|
else if ([controller.extendedGamepad isKindOfClass:[GCDualShockGamepad class]]) {
|
|
GCDualShockGamepad* dualShockGamepad = (GCDualShockGamepad*)controller.extendedGamepad;
|
|
|
|
if (dualShockGamepad.touchpadButton) {
|
|
supportedButtonFlags |= TOUCHPAD_FLAG;
|
|
capabilities |= LI_CCAP_TOUCHPAD;
|
|
}
|
|
|
|
type = LI_CTYPE_PS;
|
|
}
|
|
|
|
if (@available(iOS 14.5, tvOS 14.5, *)) {
|
|
if ([controller.extendedGamepad isKindOfClass:[GCDualSenseGamepad class]]) {
|
|
GCDualSenseGamepad* dualSenseGamepad = (GCDualSenseGamepad*)controller.extendedGamepad;
|
|
|
|
if (dualSenseGamepad.touchpadButton) {
|
|
supportedButtonFlags |= TOUCHPAD_FLAG;
|
|
capabilities |= LI_CCAP_TOUCHPAD;
|
|
}
|
|
|
|
type = LI_CTYPE_PS;
|
|
}
|
|
}
|
|
|
|
// Detect supported haptics localities
|
|
if (controller.haptics) {
|
|
if ([controller.haptics.supportedLocalities containsObject:GCHapticsLocalityHandles]) {
|
|
capabilities |= LI_CCAP_RUMBLE;
|
|
}
|
|
if ([controller.haptics.supportedLocalities containsObject:GCHapticsLocalityTriggers]) {
|
|
capabilities |= LI_CCAP_TRIGGER_RUMBLE;
|
|
}
|
|
}
|
|
|
|
// Detect supported motion sensors
|
|
if (controller.motion) {
|
|
if (controller.motion.hasGravityAndUserAcceleration) {
|
|
capabilities |= LI_CCAP_ACCEL;
|
|
}
|
|
if (controller.motion.hasRotationRate) {
|
|
capabilities |= LI_CCAP_GYRO;
|
|
}
|
|
}
|
|
|
|
LiSendControllerArrivalEvent(controller.playerIndex, [self getActiveGamepadMask], type, supportedButtonFlags, capabilities);
|
|
}
|
|
}
|
|
|
|
-(void) handleControllerTouchpad:(Controller*)controller primaryTouch:(GCControllerDirectionPad*)primaryTouch secondaryTouch:(GCControllerDirectionPad*)secondaryTouch
|
|
{
|
|
// If we went from a touch to no touch, generate a touch up event
|
|
if ((controller.lastPrimaryTouchX || controller.lastPrimaryTouchY) && (!primaryTouch.xAxis.value && !primaryTouch.yAxis.value)) {
|
|
LiSendTouchEvent(LI_TOUCH_EVENT_UP, 0, primaryTouch.xAxis.value, primaryTouch.yAxis.value, 1.0f);
|
|
}
|
|
else if (primaryTouch.xAxis.value || primaryTouch.yAxis.value) {
|
|
// If we went from no touch to a touch, generate a touch down event
|
|
if (!controller.lastPrimaryTouchX && !controller.lastPrimaryTouchY) {
|
|
LiSendTouchEvent(LI_TOUCH_EVENT_DOWN, 0, primaryTouch.xAxis.value, primaryTouch.yAxis.value, 1.0f);
|
|
}
|
|
else if (controller.lastPrimaryTouchX != primaryTouch.xAxis.value ||
|
|
controller.lastPrimaryTouchY != primaryTouch.yAxis.value) {
|
|
// Otherwise it's just a move
|
|
LiSendTouchEvent(LI_TOUCH_EVENT_MOVE, 0, primaryTouch.xAxis.value, primaryTouch.yAxis.value, 1.0f);
|
|
}
|
|
}
|
|
controller.lastPrimaryTouchX = primaryTouch.xAxis.value;
|
|
controller.lastPrimaryTouchY = primaryTouch.yAxis.value;
|
|
|
|
|
|
// If we went from a touch to no touch, generate a touch up event
|
|
if ((controller.lastSecondaryTouchX || controller.lastSecondaryTouchY) && (!secondaryTouch.xAxis.value && !secondaryTouch.yAxis.value)) {
|
|
LiSendTouchEvent(LI_TOUCH_EVENT_UP, 1, secondaryTouch.xAxis.value, secondaryTouch.yAxis.value, 1.0f);
|
|
}
|
|
else if (secondaryTouch.xAxis.value || secondaryTouch.yAxis.value) {
|
|
// If we went from no touch to a touch, generate a touch down event
|
|
if (!controller.lastSecondaryTouchX && !controller.lastSecondaryTouchY) {
|
|
LiSendTouchEvent(LI_TOUCH_EVENT_DOWN, 1, secondaryTouch.xAxis.value, secondaryTouch.yAxis.value, 1.0f);
|
|
}
|
|
else if (controller.lastSecondaryTouchX != secondaryTouch.xAxis.value ||
|
|
controller.lastSecondaryTouchY != secondaryTouch.yAxis.value) {
|
|
// Otherwise it's just a move
|
|
LiSendTouchEvent(LI_TOUCH_EVENT_MOVE, 1, secondaryTouch.xAxis.value, secondaryTouch.yAxis.value, 1.0f);
|
|
}
|
|
}
|
|
controller.lastSecondaryTouchX = secondaryTouch.xAxis.value;
|
|
controller.lastSecondaryTouchY = secondaryTouch.yAxis.value;
|
|
}
|
|
|
|
-(void) registerControllerCallbacks:(GCController*) controller
|
|
{
|
|
if (controller != NULL) {
|
|
// iOS 13 allows the Start button to behave like a normal button, however
|
|
// older MFi controllers can send an instant down+up event for the start button
|
|
// which means the button will not be down long enough to register on the PC.
|
|
// To work around this issue, use the old controllerPausedHandler if the controller
|
|
// doesn't have a Select button (which indicates it probably doesn't have a proper
|
|
// Start button either).
|
|
BOOL useLegacyPausedHandler = YES;
|
|
if (@available(iOS 13.0, tvOS 13.0, *)) {
|
|
if (controller.extendedGamepad != nil &&
|
|
controller.extendedGamepad.buttonOptions != nil) {
|
|
useLegacyPausedHandler = NO;
|
|
}
|
|
}
|
|
|
|
if (useLegacyPausedHandler) {
|
|
controller.controllerPausedHandler = ^(GCController *controller) {
|
|
Controller* limeController = [self->_controllers objectForKey:[NSNumber numberWithInteger:controller.playerIndex]];
|
|
|
|
// Get off the main thread
|
|
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
|
|
[self setButtonFlag:limeController flags:PLAY_FLAG];
|
|
[self updateFinished:limeController];
|
|
|
|
// Pause for 100 ms
|
|
usleep(100 * 1000);
|
|
|
|
[self clearButtonFlag:limeController flags:PLAY_FLAG];
|
|
[self updateFinished:limeController];
|
|
});
|
|
};
|
|
}
|
|
|
|
if (controller.extendedGamepad != NULL) {
|
|
// Disable system gestures on the gamepad to avoid interfering
|
|
// with in-game controller actions
|
|
if (@available(iOS 14.0, tvOS 14.0, *)) {
|
|
for (GCControllerElement* element in controller.extendedGamepad.allElements) {
|
|
element.preferredSystemGestureState = GCSystemGestureStateDisabled;
|
|
}
|
|
}
|
|
|
|
controller.extendedGamepad.valueChangedHandler = ^(GCExtendedGamepad *gamepad, GCControllerElement *element) {
|
|
Controller* limeController = [self->_controllers objectForKey:[NSNumber numberWithInteger:gamepad.controller.playerIndex]];
|
|
short leftStickX, leftStickY;
|
|
short rightStickX, rightStickY;
|
|
unsigned char leftTrigger, rightTrigger;
|
|
|
|
if (self->_swapABXYButtons) {
|
|
UPDATE_BUTTON_FLAG(limeController, B_FLAG, gamepad.buttonA.pressed);
|
|
UPDATE_BUTTON_FLAG(limeController, A_FLAG, gamepad.buttonB.pressed);
|
|
UPDATE_BUTTON_FLAG(limeController, Y_FLAG, gamepad.buttonX.pressed);
|
|
UPDATE_BUTTON_FLAG(limeController, X_FLAG, gamepad.buttonY.pressed);
|
|
}
|
|
else {
|
|
UPDATE_BUTTON_FLAG(limeController, A_FLAG, gamepad.buttonA.pressed);
|
|
UPDATE_BUTTON_FLAG(limeController, B_FLAG, gamepad.buttonB.pressed);
|
|
UPDATE_BUTTON_FLAG(limeController, X_FLAG, gamepad.buttonX.pressed);
|
|
UPDATE_BUTTON_FLAG(limeController, Y_FLAG, gamepad.buttonY.pressed);
|
|
}
|
|
|
|
UPDATE_BUTTON_FLAG(limeController, UP_FLAG, gamepad.dpad.up.pressed);
|
|
UPDATE_BUTTON_FLAG(limeController, DOWN_FLAG, gamepad.dpad.down.pressed);
|
|
UPDATE_BUTTON_FLAG(limeController, LEFT_FLAG, gamepad.dpad.left.pressed);
|
|
UPDATE_BUTTON_FLAG(limeController, RIGHT_FLAG, gamepad.dpad.right.pressed);
|
|
|
|
UPDATE_BUTTON_FLAG(limeController, LB_FLAG, gamepad.leftShoulder.pressed);
|
|
UPDATE_BUTTON_FLAG(limeController, RB_FLAG, gamepad.rightShoulder.pressed);
|
|
|
|
// Yay, iOS 12.1 now supports analog stick buttons
|
|
if (@available(iOS 12.1, tvOS 12.1, *)) {
|
|
if (gamepad.leftThumbstickButton != nil) {
|
|
UPDATE_BUTTON_FLAG(limeController, LS_CLK_FLAG, gamepad.leftThumbstickButton.pressed);
|
|
}
|
|
if (gamepad.rightThumbstickButton != nil) {
|
|
UPDATE_BUTTON_FLAG(limeController, RS_CLK_FLAG, gamepad.rightThumbstickButton.pressed);
|
|
}
|
|
}
|
|
|
|
if (@available(iOS 13.0, tvOS 13.0, *)) {
|
|
// Options button is optional (only present on Xbox One S and PS4 gamepads)
|
|
if (gamepad.buttonOptions != nil) {
|
|
UPDATE_BUTTON_FLAG(limeController, BACK_FLAG, gamepad.buttonOptions.pressed);
|
|
|
|
// For older MFi gamepads, the menu button will already be handled by
|
|
// the controllerPausedHandler.
|
|
UPDATE_BUTTON_FLAG(limeController, PLAY_FLAG, gamepad.buttonMenu.pressed);
|
|
}
|
|
}
|
|
|
|
if (@available(iOS 14.0, tvOS 14.0, *)) {
|
|
// Home/Guide button is optional (only present on Xbox One S and PS4 gamepads)
|
|
if (gamepad.buttonHome != nil) {
|
|
UPDATE_BUTTON_FLAG(limeController, SPECIAL_FLAG, gamepad.buttonHome.pressed);
|
|
}
|
|
|
|
// iOS 14 adds specific profiles for DualShock and Xbox gamepads with additional buttons
|
|
if ([gamepad isKindOfClass:[GCXboxGamepad class]]) {
|
|
GCXboxGamepad* xboxGamepad = (GCXboxGamepad*)gamepad;
|
|
|
|
if (xboxGamepad.paddleButton1) {
|
|
UPDATE_BUTTON_FLAG(limeController, PADDLE1_FLAG, xboxGamepad.paddleButton1.pressed);
|
|
}
|
|
if (xboxGamepad.paddleButton2) {
|
|
UPDATE_BUTTON_FLAG(limeController, PADDLE2_FLAG, xboxGamepad.paddleButton2.pressed);
|
|
}
|
|
if (xboxGamepad.paddleButton3) {
|
|
UPDATE_BUTTON_FLAG(limeController, PADDLE3_FLAG, xboxGamepad.paddleButton3.pressed);
|
|
}
|
|
if (xboxGamepad.paddleButton4) {
|
|
UPDATE_BUTTON_FLAG(limeController, PADDLE4_FLAG, xboxGamepad.paddleButton4.pressed);
|
|
}
|
|
|
|
if (@available(iOS 15.0, tvOS 15.0, *)) {
|
|
if (xboxGamepad.buttonShare) {
|
|
UPDATE_BUTTON_FLAG(limeController, MISC_FLAG, xboxGamepad.buttonShare.pressed);
|
|
}
|
|
}
|
|
}
|
|
else if ([gamepad isKindOfClass:[GCDualShockGamepad class]]) {
|
|
GCDualShockGamepad* dualShockGamepad = (GCDualShockGamepad*)gamepad;
|
|
|
|
if (dualShockGamepad.touchpadButton) {
|
|
UPDATE_BUTTON_FLAG(limeController, TOUCHPAD_FLAG, dualShockGamepad.touchpadButton.pressed);
|
|
}
|
|
|
|
[self handleControllerTouchpad:limeController
|
|
primaryTouch:dualShockGamepad.touchpadPrimary
|
|
secondaryTouch:dualShockGamepad.touchpadSecondary];
|
|
}
|
|
|
|
if (@available(iOS 14.5, tvOS 14.5, *)) {
|
|
if ([gamepad isKindOfClass:[GCDualSenseGamepad class]]) {
|
|
GCDualSenseGamepad* dualSenseGamepad = (GCDualSenseGamepad*)gamepad;
|
|
|
|
if (dualSenseGamepad.touchpadButton) {
|
|
UPDATE_BUTTON_FLAG(limeController, TOUCHPAD_FLAG, dualSenseGamepad.touchpadButton.pressed);
|
|
}
|
|
|
|
[self handleControllerTouchpad:limeController
|
|
primaryTouch:dualSenseGamepad.touchpadPrimary
|
|
secondaryTouch:dualSenseGamepad.touchpadSecondary];
|
|
}
|
|
}
|
|
}
|
|
|
|
leftStickX = gamepad.leftThumbstick.xAxis.value * 0x7FFE;
|
|
leftStickY = gamepad.leftThumbstick.yAxis.value * 0x7FFE;
|
|
|
|
rightStickX = gamepad.rightThumbstick.xAxis.value * 0x7FFE;
|
|
rightStickY = gamepad.rightThumbstick.yAxis.value * 0x7FFE;
|
|
|
|
leftTrigger = gamepad.leftTrigger.value * 0xFF;
|
|
rightTrigger = gamepad.rightTrigger.value * 0xFF;
|
|
|
|
[self updateLeftStick:limeController x:leftStickX y:leftStickY];
|
|
[self updateRightStick:limeController x:rightStickX y:rightStickY];
|
|
[self updateTriggers:limeController left:leftTrigger right:rightTrigger];
|
|
[self updateFinished:limeController];
|
|
};
|
|
}
|
|
else if (controller.gamepad != NULL) {
|
|
controller.gamepad.valueChangedHandler = ^(GCGamepad *gamepad, GCControllerElement *element) {
|
|
Controller* limeController = [self->_controllers objectForKey:[NSNumber numberWithInteger:gamepad.controller.playerIndex]];
|
|
if (self->_swapABXYButtons) {
|
|
UPDATE_BUTTON_FLAG(limeController, B_FLAG, gamepad.buttonA.pressed);
|
|
UPDATE_BUTTON_FLAG(limeController, A_FLAG, gamepad.buttonB.pressed);
|
|
UPDATE_BUTTON_FLAG(limeController, Y_FLAG, gamepad.buttonX.pressed);
|
|
UPDATE_BUTTON_FLAG(limeController, X_FLAG, gamepad.buttonY.pressed);
|
|
}
|
|
else {
|
|
UPDATE_BUTTON_FLAG(limeController, A_FLAG, gamepad.buttonA.pressed);
|
|
UPDATE_BUTTON_FLAG(limeController, B_FLAG, gamepad.buttonB.pressed);
|
|
UPDATE_BUTTON_FLAG(limeController, X_FLAG, gamepad.buttonX.pressed);
|
|
UPDATE_BUTTON_FLAG(limeController, Y_FLAG, gamepad.buttonY.pressed);
|
|
}
|
|
|
|
UPDATE_BUTTON_FLAG(limeController, UP_FLAG, gamepad.dpad.up.pressed);
|
|
UPDATE_BUTTON_FLAG(limeController, DOWN_FLAG, gamepad.dpad.down.pressed);
|
|
UPDATE_BUTTON_FLAG(limeController, LEFT_FLAG, gamepad.dpad.left.pressed);
|
|
UPDATE_BUTTON_FLAG(limeController, RIGHT_FLAG, gamepad.dpad.right.pressed);
|
|
|
|
UPDATE_BUTTON_FLAG(limeController, LB_FLAG, gamepad.leftShoulder.pressed);
|
|
UPDATE_BUTTON_FLAG(limeController, RB_FLAG, gamepad.rightShoulder.pressed);
|
|
|
|
[self updateFinished:limeController];
|
|
};
|
|
}
|
|
} else {
|
|
Log(LOG_W, @"Tried to register controller callbacks on NULL controller");
|
|
}
|
|
}
|
|
|
|
-(void) unregisterMouseCallbacks:(GCMouse*)mouse API_AVAILABLE(ios(14.0)) {
|
|
mouse.mouseInput.mouseMovedHandler = nil;
|
|
|
|
mouse.mouseInput.leftButton.pressedChangedHandler = nil;
|
|
mouse.mouseInput.middleButton.pressedChangedHandler = nil;
|
|
mouse.mouseInput.rightButton.pressedChangedHandler = nil;
|
|
|
|
for (GCControllerButtonInput* auxButton in mouse.mouseInput.auxiliaryButtons) {
|
|
auxButton.pressedChangedHandler = nil;
|
|
}
|
|
|
|
#if TARGET_OS_TV
|
|
mouse.mouseInput.scroll.xAxis.valueChangedHandler = nil;
|
|
mouse.mouseInput.scroll.yAxis.valueChangedHandler = nil;
|
|
#endif
|
|
}
|
|
|
|
-(void) registerMouseCallbacks:(GCMouse*) mouse API_AVAILABLE(ios(14.0)) {
|
|
mouse.mouseInput.mouseMovedHandler = ^(GCMouseInput * _Nonnull mouse, float deltaX, float deltaY) {
|
|
self->accumulatedDeltaX += deltaX / MOUSE_SPEED_DIVISOR;
|
|
self->accumulatedDeltaY += -deltaY / MOUSE_SPEED_DIVISOR;
|
|
|
|
short truncatedDeltaX = (short)self->accumulatedDeltaX;
|
|
short truncatedDeltaY = (short)self->accumulatedDeltaY;
|
|
|
|
if (truncatedDeltaX != 0 || truncatedDeltaY != 0) {
|
|
LiSendMouseMoveEvent(truncatedDeltaX, truncatedDeltaY);
|
|
|
|
self->accumulatedDeltaX -= truncatedDeltaX;
|
|
self->accumulatedDeltaY -= truncatedDeltaY;
|
|
}
|
|
};
|
|
|
|
mouse.mouseInput.leftButton.pressedChangedHandler = ^(GCControllerButtonInput * _Nonnull button, float value, BOOL pressed) {
|
|
LiSendMouseButtonEvent(pressed ? BUTTON_ACTION_PRESS : BUTTON_ACTION_RELEASE, BUTTON_LEFT);
|
|
};
|
|
mouse.mouseInput.middleButton.pressedChangedHandler = ^(GCControllerButtonInput * _Nonnull button, float value, BOOL pressed) {
|
|
LiSendMouseButtonEvent(pressed ? BUTTON_ACTION_PRESS : BUTTON_ACTION_RELEASE, BUTTON_MIDDLE);
|
|
};
|
|
mouse.mouseInput.rightButton.pressedChangedHandler = ^(GCControllerButtonInput * _Nonnull button, float value, BOOL pressed) {
|
|
LiSendMouseButtonEvent(pressed ? BUTTON_ACTION_PRESS : BUTTON_ACTION_RELEASE, BUTTON_RIGHT);
|
|
};
|
|
|
|
if (mouse.mouseInput.auxiliaryButtons != nil) {
|
|
if (mouse.mouseInput.auxiliaryButtons.count >= 1) {
|
|
mouse.mouseInput.auxiliaryButtons[0].pressedChangedHandler = ^(GCControllerButtonInput * _Nonnull button, float value, BOOL pressed) {
|
|
LiSendMouseButtonEvent(pressed ? BUTTON_ACTION_PRESS : BUTTON_ACTION_RELEASE, BUTTON_X1);
|
|
};
|
|
}
|
|
if (mouse.mouseInput.auxiliaryButtons.count >= 2) {
|
|
mouse.mouseInput.auxiliaryButtons[1].pressedChangedHandler = ^(GCControllerButtonInput * _Nonnull button, float value, BOOL pressed) {
|
|
LiSendMouseButtonEvent(pressed ? BUTTON_ACTION_PRESS : BUTTON_ACTION_RELEASE, BUTTON_X2);
|
|
};
|
|
}
|
|
}
|
|
|
|
// We use UIPanGestureRecognizer on iPadOS because it allows us to distinguish
|
|
// between discrete and continuous scroll events and also works around a bug
|
|
// in iPadOS 15 where discrete scroll events are dropped. tvOS only supports
|
|
// GCMouse for mice, so we will have to just use it and hope for the best.
|
|
#if TARGET_OS_TV
|
|
mouse.mouseInput.scroll.xAxis.valueChangedHandler = ^(GCControllerAxisInput * _Nonnull axis, float value) {
|
|
self->accumulatedScrollX += value;
|
|
|
|
short truncatedScrollX = (short)self->accumulatedScrollX;
|
|
|
|
if (truncatedScrollX != 0) {
|
|
// Direction is reversed from vertical scrolling
|
|
LiSendHighResHScrollEvent(-truncatedScrollX * 20);
|
|
|
|
self->accumulatedScrollX -= truncatedScrollX;
|
|
}
|
|
};
|
|
mouse.mouseInput.scroll.yAxis.valueChangedHandler = ^(GCControllerAxisInput * _Nonnull axis, float value) {
|
|
self->accumulatedScrollY += value;
|
|
|
|
short truncatedScrollY = (short)self->accumulatedScrollY;
|
|
|
|
if (truncatedScrollY != 0) {
|
|
LiSendHighResScrollEvent(truncatedScrollY * 20);
|
|
|
|
self->accumulatedScrollY -= truncatedScrollY;
|
|
}
|
|
};
|
|
#endif
|
|
}
|
|
|
|
-(void) updateAutoOnScreenControlMode
|
|
{
|
|
// Auto on-screen control support may not be enabled
|
|
if (_osc == NULL) {
|
|
return;
|
|
}
|
|
|
|
OnScreenControlsLevel level = OnScreenControlsLevelFull;
|
|
|
|
// We currently stop after the first controller we find.
|
|
// Maybe we'll want to change that logic later.
|
|
for (int i = 0; i < [[GCController controllers] count]; i++) {
|
|
GCController *controller = [GCController controllers][i];
|
|
|
|
if (controller != NULL) {
|
|
if (controller.extendedGamepad != NULL) {
|
|
level = OnScreenControlsLevelAutoGCExtendedGamepad;
|
|
if (@available(iOS 12.1, tvOS 12.1, *)) {
|
|
if (controller.extendedGamepad.leftThumbstickButton != nil &&
|
|
controller.extendedGamepad.rightThumbstickButton != nil) {
|
|
level = OnScreenControlsLevelAutoGCExtendedGamepadWithStickButtons;
|
|
if (@available(iOS 13.0, tvOS 13.0, *)) {
|
|
if (controller.extendedGamepad.buttonOptions != nil) {
|
|
// Has L3/R3 and Select, so we can show nothing :)
|
|
level = OnScreenControlsLevelOff;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
else if (controller.gamepad != NULL) {
|
|
level = OnScreenControlsLevelAutoGCGamepad;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we didn't find a gamepad present and we have a keyboard or mouse, turn
|
|
// the on-screen controls off to get the overlays out of the way.
|
|
if (level == OnScreenControlsLevelFull && [ControllerSupport hasKeyboardOrMouse]) {
|
|
level = OnScreenControlsLevelOff;
|
|
|
|
// Ensure the virtual gamepad disappears to avoid confusing some games.
|
|
// If the mouse and keyboard disconnect later, it will reappear when the
|
|
// first OSC input is received.
|
|
LiSendMultiControllerEvent(0, 0, 0, 0, 0, 0, 0, 0, 0);
|
|
}
|
|
|
|
[_osc setLevel:level];
|
|
}
|
|
|
|
-(void) initAutoOnScreenControlMode:(OnScreenControls*)osc
|
|
{
|
|
_osc = osc;
|
|
|
|
[self updateAutoOnScreenControlMode];
|
|
}
|
|
|
|
-(void) assignController:(GCController*)controller {
|
|
for (int i = 0; i < 4; i++) {
|
|
if (!(_controllerNumbers & (1 << i))) {
|
|
_controllerNumbers |= (1 << i);
|
|
controller.playerIndex = i;
|
|
|
|
Controller* limeController;
|
|
|
|
if (i == 0) {
|
|
// Player 0 shares a controller object with the on-screen controls
|
|
limeController = _player0osc;
|
|
} else {
|
|
limeController = [[Controller alloc] init];
|
|
limeController.playerIndex = i;
|
|
}
|
|
|
|
limeController.supportedEmulationFlags = EMULATING_SPECIAL | EMULATING_SELECT;
|
|
limeController.gamepad = controller;
|
|
|
|
if (@available(iOS 13.0, tvOS 13.0, *)) {
|
|
if (controller.extendedGamepad != nil &&
|
|
controller.extendedGamepad.buttonOptions != nil) {
|
|
// Disable select button emulation since we have a physical select button
|
|
limeController.supportedEmulationFlags &= ~EMULATING_SELECT;
|
|
}
|
|
}
|
|
|
|
if (@available(iOS 14.0, tvOS 14.0, *)) {
|
|
if (controller.extendedGamepad != nil &&
|
|
controller.extendedGamepad.buttonHome != nil) {
|
|
// Disable special button emulation since we have a physical special button
|
|
limeController.supportedEmulationFlags &= ~EMULATING_SPECIAL;
|
|
}
|
|
}
|
|
|
|
// Prepare controller haptics for use
|
|
[self initializeControllerHaptics:limeController];
|
|
|
|
[_controllers setObject:limeController forKey:[NSNumber numberWithInteger:controller.playerIndex]];
|
|
|
|
Log(LOG_I, @"Assigning controller index: %d", i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
-(Controller*) getOscController {
|
|
return _player0osc;
|
|
}
|
|
|
|
+(bool) isSupportedGamepad:(GCController*) controller {
|
|
return controller.extendedGamepad != nil || controller.gamepad != nil;
|
|
}
|
|
|
|
#pragma clang diagnostic pop
|
|
|
|
+(int) getGamepadCount {
|
|
int count = 0;
|
|
|
|
for (GCController* controller in [GCController controllers]) {
|
|
if ([ControllerSupport isSupportedGamepad:controller]) {
|
|
count++;
|
|
}
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
+(int) getConnectedGamepadMask:(StreamConfiguration*)streamConfig {
|
|
int mask = 0;
|
|
|
|
if (streamConfig.multiController) {
|
|
int i = 0;
|
|
for (GCController* controller in [GCController controllers]) {
|
|
if ([ControllerSupport isSupportedGamepad:controller]) {
|
|
mask |= 1 << i++;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
// Some games don't deal with having controller reconnected
|
|
// properly so always report controller 1 if not in MC mode
|
|
mask = 0x1;
|
|
}
|
|
|
|
DataManager* dataMan = [[DataManager alloc] init];
|
|
TemporarySettings* settings = [dataMan getSettings];
|
|
OnScreenControlsLevel level = (OnScreenControlsLevel)[settings.onscreenControls integerValue];
|
|
|
|
// Even if no gamepads are present, we will always count one if OSC is enabled,
|
|
// or it's set to auto and no keyboard or mouse is present. Absolute touch mode
|
|
// disables the OSC.
|
|
if (level != OnScreenControlsLevelOff && (![ControllerSupport hasKeyboardOrMouse] || level != OnScreenControlsLevelAuto) && !settings.absoluteTouchMode) {
|
|
mask |= 0x1;
|
|
}
|
|
|
|
return mask;
|
|
}
|
|
|
|
-(NSUInteger) getConnectedGamepadCount
|
|
{
|
|
return _controllers.count;
|
|
}
|
|
|
|
-(id) initWithConfig:(StreamConfiguration*)streamConfig delegate:(id<ControllerSupportDelegate>)delegate
|
|
{
|
|
self = [super init];
|
|
|
|
_controllerStreamLock = [[NSLock alloc] init];
|
|
_controllers = [[NSMutableDictionary alloc] init];
|
|
_controllerNumbers = 0;
|
|
_multiController = streamConfig.multiController;
|
|
_swapABXYButtons = streamConfig.swapABXYButtons;
|
|
_delegate = delegate;
|
|
|
|
_player0osc = [[Controller alloc] init];
|
|
_player0osc.playerIndex = 0;
|
|
|
|
DataManager* dataMan = [[DataManager alloc] init];
|
|
_oscEnabled = (OnScreenControlsLevel)[[dataMan getSettings].onscreenControls integerValue] != OnScreenControlsLevelOff;
|
|
|
|
Log(LOG_I, @"Number of supported controllers connected: %d", [ControllerSupport getGamepadCount]);
|
|
Log(LOG_I, @"Multi-controller: %d", _multiController);
|
|
|
|
for (GCController* controller in [GCController controllers]) {
|
|
if ([ControllerSupport isSupportedGamepad:controller]) {
|
|
[self assignController:controller];
|
|
[self registerControllerCallbacks:controller];
|
|
|
|
// Note: We cannot report controller arrival to the host here,
|
|
// because the connection has not been established yet.
|
|
}
|
|
}
|
|
|
|
if (@available(iOS 14.0, tvOS 14.0, *)) {
|
|
for (GCMouse* mouse in [GCMouse mice]) {
|
|
[self registerMouseCallbacks:mouse];
|
|
}
|
|
}
|
|
|
|
_controllerConnectObserver = [[NSNotificationCenter defaultCenter] addObserverForName:GCControllerDidConnectNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
|
|
Log(LOG_I, @"Controller connected!");
|
|
|
|
GCController* controller = note.object;
|
|
|
|
if (![ControllerSupport isSupportedGamepad:controller]) {
|
|
// Ignore micro gamepads and motion controllers
|
|
return;
|
|
}
|
|
|
|
[self assignController:controller];
|
|
|
|
// Register callbacks on the new controller
|
|
[self registerControllerCallbacks:controller];
|
|
|
|
// Report the controller arrival to the host
|
|
[self reportControllerArrival:controller];
|
|
|
|
// Re-evaluate the on-screen control mode
|
|
[self updateAutoOnScreenControlMode];
|
|
|
|
// Notify the delegate
|
|
[self->_delegate gamepadPresenceChanged];
|
|
}];
|
|
_controllerDisconnectObserver = [[NSNotificationCenter defaultCenter] addObserverForName:GCControllerDidDisconnectNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
|
|
Log(LOG_I, @"Controller disconnected!");
|
|
|
|
GCController* controller = note.object;
|
|
|
|
if (![ControllerSupport isSupportedGamepad:controller]) {
|
|
// Ignore micro gamepads and motion controllers
|
|
return;
|
|
}
|
|
|
|
[self unregisterControllerCallbacks:controller];
|
|
self->_controllerNumbers &= ~(1 << controller.playerIndex);
|
|
Log(LOG_I, @"Unassigning controller index: %ld", (long)controller.playerIndex);
|
|
|
|
// Unset the GCController on this object (in case it is the OSC, which will persist)
|
|
Controller* limeController = [self->_controllers objectForKey:[NSNumber numberWithInteger:controller.playerIndex]];
|
|
|
|
// Stop haptics on this controller
|
|
[self cleanupControllerHaptics:limeController];
|
|
|
|
// Stop motion reports on this controller
|
|
[self cleanupControllerMotion:limeController];
|
|
|
|
limeController.gamepad = nil;
|
|
|
|
// Inform the server of the updated active gamepads before removing this controller
|
|
[self updateFinished:limeController];
|
|
[self->_controllers removeObjectForKey:[NSNumber numberWithInteger:controller.playerIndex]];
|
|
|
|
// Re-evaluate the on-screen control mode
|
|
[self updateAutoOnScreenControlMode];
|
|
|
|
// Notify the delegate
|
|
[self->_delegate gamepadPresenceChanged];
|
|
}];
|
|
|
|
if (@available(iOS 14.0, tvOS 14.0, *)) {
|
|
_mouseConnectObserver = [[NSNotificationCenter defaultCenter] addObserverForName:GCMouseDidConnectNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
|
|
Log(LOG_I, @"Mouse connected!");
|
|
|
|
GCMouse* mouse = note.object;
|
|
|
|
// Register for mouse events
|
|
[self registerMouseCallbacks: mouse];
|
|
|
|
// Re-evaluate the on-screen control mode
|
|
[self updateAutoOnScreenControlMode];
|
|
|
|
// Notify the delegate
|
|
[self->_delegate mousePresenceChanged];
|
|
}];
|
|
_mouseDisconnectObserver = [[NSNotificationCenter defaultCenter] addObserverForName:GCMouseDidDisconnectNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
|
|
Log(LOG_I, @"Mouse disconnected!");
|
|
|
|
GCMouse* mouse = note.object;
|
|
|
|
// Unregister for mouse events
|
|
[self unregisterMouseCallbacks: mouse];
|
|
|
|
// Re-evaluate the on-screen control mode
|
|
[self updateAutoOnScreenControlMode];
|
|
|
|
// Notify the delegate
|
|
[self->_delegate mousePresenceChanged];
|
|
}];
|
|
_keyboardConnectObserver = [[NSNotificationCenter defaultCenter] addObserverForName:GCKeyboardDidConnectNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
|
|
Log(LOG_I, @"Keyboard connected!");
|
|
|
|
// Re-evaluate the on-screen control mode
|
|
[self updateAutoOnScreenControlMode];
|
|
}];
|
|
_keyboardDisconnectObserver = [[NSNotificationCenter defaultCenter] addObserverForName:GCKeyboardDidDisconnectNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
|
|
Log(LOG_I, @"Keyboard disconnected!");
|
|
|
|
// Re-evaluate the on-screen control mode
|
|
[self updateAutoOnScreenControlMode];
|
|
}];
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
-(void) cleanup
|
|
{
|
|
[[NSNotificationCenter defaultCenter] removeObserver:_controllerConnectObserver];
|
|
[[NSNotificationCenter defaultCenter] removeObserver:_controllerDisconnectObserver];
|
|
[[NSNotificationCenter defaultCenter] removeObserver:_mouseConnectObserver];
|
|
[[NSNotificationCenter defaultCenter] removeObserver:_mouseDisconnectObserver];
|
|
[[NSNotificationCenter defaultCenter] removeObserver:_keyboardConnectObserver];
|
|
[[NSNotificationCenter defaultCenter] removeObserver:_keyboardDisconnectObserver];
|
|
|
|
_controllerConnectObserver = nil;
|
|
_controllerDisconnectObserver = nil;
|
|
_mouseConnectObserver = nil;
|
|
_mouseDisconnectObserver = nil;
|
|
_keyboardConnectObserver = nil;
|
|
_keyboardDisconnectObserver = nil;
|
|
|
|
_controllerNumbers = 0;
|
|
|
|
for (Controller* controller in [_controllers allValues]) {
|
|
[self cleanupControllerHaptics:controller];
|
|
[self cleanupControllerMotion:controller];
|
|
}
|
|
[_controllers removeAllObjects];
|
|
|
|
for (GCController* controller in [GCController controllers]) {
|
|
if ([ControllerSupport isSupportedGamepad:controller]) {
|
|
[self unregisterControllerCallbacks:controller];
|
|
}
|
|
}
|
|
|
|
if (@available(iOS 14.0, tvOS 14.0, *)) {
|
|
for (GCMouse* mouse in [GCMouse mice]) {
|
|
[self unregisterMouseCallbacks:mouse];
|
|
}
|
|
}
|
|
}
|
|
|
|
@end
|