// // 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