mirror of
https://github.com/moonlight-stream/moonlight-ios.git
synced 2025-07-27 14:45:34 +00:00
347 lines
13 KiB
Objective-C
347 lines
13 KiB
Objective-C
//
|
|
// ControllerSupport.m
|
|
// Limelight
|
|
//
|
|
// Created by Cameron Gutman on 10/20/14.
|
|
// Copyright (c) 2014 Limelight Stream. All rights reserved.
|
|
//
|
|
|
|
#import "ControllerSupport.h"
|
|
#import "OnScreenControls.h"
|
|
#import "Controller.h"
|
|
#include "Limelight.h"
|
|
|
|
@import GameController;
|
|
|
|
@implementation ControllerSupport {
|
|
NSLock *_controllerStreamLock;
|
|
NSMutableDictionary *_controllers;
|
|
|
|
OnScreenControls *_osc;
|
|
|
|
char _controllerNumbers;
|
|
|
|
#define EMULATING_SELECT 0x1
|
|
#define EMULATING_SPECIAL 0x2
|
|
int _emulatingButtonFlags;
|
|
}
|
|
|
|
// UPDATE_BUTTON_FLAG(flag, pressed)
|
|
#define UPDATE_BUTTON_FLAG(controller, x, y) \
|
|
((y) ? [self setButtonFlag:controller flags:x] : [self clearButtonFlag:controller flags:x])
|
|
|
|
-(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:(char)left
|
|
{
|
|
@synchronized(controller) {
|
|
controller.lastLeftTrigger = left;
|
|
}
|
|
}
|
|
|
|
-(void) updateRightTrigger:(Controller*)controller right:(char)right
|
|
{
|
|
@synchronized(controller) {
|
|
controller.lastRightTrigger = right;
|
|
}
|
|
}
|
|
|
|
-(void) updateTriggers:(Controller*) controller left:(char)left right:(char)right
|
|
{
|
|
@synchronized(controller) {
|
|
controller.lastLeftTrigger = left;
|
|
controller.lastRightTrigger = right;
|
|
}
|
|
}
|
|
|
|
-(void) handleSpecialCombosReleased:(Controller*)controller releasedButtons:(int)releasedButtons
|
|
{
|
|
if ((_emulatingButtonFlags & EMULATING_SELECT) &&
|
|
((releasedButtons & LB_FLAG) || (releasedButtons & PLAY_FLAG))) {
|
|
controller.lastButtonFlags &= ~BACK_FLAG;
|
|
_emulatingButtonFlags &= ~EMULATING_SELECT;
|
|
}
|
|
if ((_emulatingButtonFlags & EMULATING_SPECIAL) &&
|
|
((releasedButtons & RB_FLAG) || (releasedButtons & PLAY_FLAG) ||
|
|
(releasedButtons & BACK_FLAG))) {
|
|
controller.lastButtonFlags &= ~SPECIAL_FLAG;
|
|
_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) {
|
|
controller.lastButtonFlags |= BACK_FLAG;
|
|
controller.lastButtonFlags &= ~(pressedButtons & (PLAY_FLAG | LB_FLAG));
|
|
_emulatingButtonFlags |= EMULATING_SELECT;
|
|
}
|
|
// If (RB or select) and start are down, trigger special
|
|
else if ((controller.lastButtonFlags & RB_FLAG) || (controller.lastButtonFlags & BACK_FLAG)) {
|
|
controller.lastButtonFlags |= SPECIAL_FLAG;
|
|
controller.lastButtonFlags &= ~(pressedButtons & (PLAY_FLAG | RB_FLAG | BACK_FLAG));
|
|
_emulatingButtonFlags |= EMULATING_SPECIAL;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
-(void) updateButtonFlags:(Controller*)controller flags:(int)flags
|
|
{
|
|
@synchronized(controller) {
|
|
int releasedButtons = (controller.lastButtonFlags ^ flags) & ~flags;
|
|
int pressedButtons = (controller.lastButtonFlags ^ flags) & flags;
|
|
|
|
controller.lastButtonFlags = flags;
|
|
|
|
// This must be called before handleSpecialCombosPressed
|
|
// because we clear the original button flags there
|
|
[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];
|
|
}
|
|
}
|
|
|
|
-(void) updateFinished:(Controller*)controller
|
|
{
|
|
[_controllerStreamLock lock];
|
|
@synchronized(controller) {
|
|
LiSendControllerEvent(controller.lastButtonFlags, controller.lastLeftTrigger, controller.lastRightTrigger, controller.lastLeftStickX, controller.lastLeftStickY, controller.lastRightStickX, controller.lastRightStickY);
|
|
}
|
|
[_controllerStreamLock unlock];
|
|
}
|
|
|
|
-(void) unregisterControllerCallbacks
|
|
{
|
|
for (int i = 0; i < [[GCController controllers] count]; i++) {
|
|
GCController *controller = [GCController controllers][i];
|
|
|
|
if (controller != NULL) {
|
|
controller.controllerPausedHandler = NULL;
|
|
|
|
if (controller.extendedGamepad != NULL) {
|
|
controller.extendedGamepad.valueChangedHandler = NULL;
|
|
}
|
|
else if (controller.gamepad != NULL) {
|
|
controller.gamepad.valueChangedHandler = NULL;
|
|
}
|
|
}
|
|
}
|
|
[_controllers removeAllObjects];
|
|
}
|
|
|
|
-(void) registerControllerCallbacks
|
|
{
|
|
for (int i = 0; i < [[GCController controllers] count]; i++) {
|
|
GCController *controller = [GCController controllers][i];
|
|
|
|
if (controller != NULL) {
|
|
controller.controllerPausedHandler = ^(GCController *controller) {
|
|
Controller* limeController = [_controllers objectForKey:[NSNumber numberWithInteger:controller.playerIndex]];
|
|
[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) {
|
|
controller.extendedGamepad.valueChangedHandler = ^(GCExtendedGamepad *gamepad, GCControllerElement *element) {
|
|
Controller* limeController = [_controllers objectForKey:[NSNumber numberWithInteger:gamepad.controller.playerIndex]];
|
|
short leftStickX, leftStickY;
|
|
short rightStickX, rightStickY;
|
|
char leftTrigger, rightTrigger;
|
|
|
|
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);
|
|
|
|
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 = [_controllers objectForKey:[NSNumber numberWithInteger:gamepad.controller.playerIndex]];
|
|
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];
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
-(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;
|
|
break;
|
|
}
|
|
else if (controller.gamepad != NULL) {
|
|
level = OnScreenControlsLevelAutoGCGamepad;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
[_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* limeController = [[Controller alloc] init];
|
|
controller.playerIndex = i;
|
|
limeController.playerIndex = i;
|
|
[_controllers setObject:limeController forKey:[NSNumber numberWithInteger:controller.playerIndex]];
|
|
|
|
NSLog(@"Assigning controller index: %d", i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
-(id) init
|
|
{
|
|
self = [super init];
|
|
|
|
_controllerStreamLock = [[NSLock alloc] init];
|
|
_controllers = [[NSMutableDictionary alloc] init];
|
|
_controllerNumbers = 0;
|
|
|
|
NSLog(@"Number of controllers connected: %ld", [[GCController controllers] count]);
|
|
for (GCController* controller in [GCController controllers]) {
|
|
[self assignController:controller];
|
|
}
|
|
|
|
self.connectObserver = [[NSNotificationCenter defaultCenter] addObserverForName:GCControllerDidConnectNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
|
|
NSLog(@"Controller connected!");
|
|
|
|
GCController* controller = (GCController*) note.object;
|
|
[self assignController:controller];
|
|
|
|
// Register callbacks on the new controller
|
|
[self registerControllerCallbacks];
|
|
|
|
// Re-evaluate the on-screen control mode
|
|
[self updateAutoOnScreenControlMode];
|
|
}];
|
|
self.disconnectObserver = [[NSNotificationCenter defaultCenter] addObserverForName:GCControllerDidDisconnectNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
|
|
NSLog(@"Controller disconnected!");
|
|
|
|
GCController* controller = note.object;
|
|
[_controllers removeObjectForKey:[NSNumber numberWithInteger:controller.playerIndex]];
|
|
_controllerNumbers &= ~(1 << controller.playerIndex);
|
|
NSLog(@"Unassigning controller index: %ld", (long)controller.playerIndex);
|
|
|
|
// Re-evaluate the on-screen control mode
|
|
[self updateAutoOnScreenControlMode];
|
|
}];
|
|
|
|
// Register for controller callbacks on any existing controllers
|
|
[self registerControllerCallbacks];
|
|
|
|
return self;
|
|
}
|
|
|
|
-(void) cleanup
|
|
{
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self.connectObserver];
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self.disconnectObserver];
|
|
|
|
[self unregisterControllerCallbacks];
|
|
}
|
|
|
|
@end
|