From f2ae32c45c20ce0543dd0d45961862dff82ad77b Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Mon, 20 Oct 2014 21:13:13 -0400 Subject: [PATCH] Add support for game controllers --- Limelight.xcodeproj/project.pbxproj | 6 ++ Limelight/ControllerSupport.h | 20 +++++ Limelight/ControllerSupport.m | 121 ++++++++++++++++++++++++++ Limelight/StreamFrameViewController.m | 7 +- 4 files changed, 153 insertions(+), 1 deletion(-) create mode 100644 Limelight/ControllerSupport.h create mode 100644 Limelight/ControllerSupport.m diff --git a/Limelight.xcodeproj/project.pbxproj b/Limelight.xcodeproj/project.pbxproj index 3d45ea7..02757d0 100644 --- a/Limelight.xcodeproj/project.pbxproj +++ b/Limelight.xcodeproj/project.pbxproj @@ -11,6 +11,7 @@ 98A03B4D19F352EB00861ACA /* liblimelight-common.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 98A03B4A19F3514B00861ACA /* liblimelight-common.a */; }; 98A03B5019F3598400861ACA /* VideoDecoderRenderer.m in Sources */ = {isa = PBXBuildFile; fileRef = 98A03B4F19F3598400861ACA /* VideoDecoderRenderer.m */; }; 98A03B5119F35AAC00861ACA /* libcrypto.a in Frameworks */ = {isa = PBXBuildFile; fileRef = FBCC0E9819EF9703009729EB /* libcrypto.a */; }; + 98B175B419F5DAFC00DA2AED /* ControllerSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 98B175B319F5DAFC00DA2AED /* ControllerSupport.m */; }; FB290CF219B2C406004C83CF /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FB290CF119B2C406004C83CF /* Foundation.framework */; }; FB290CF419B2C406004C83CF /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FB290CF319B2C406004C83CF /* CoreGraphics.framework */; }; FB290CF619B2C406004C83CF /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FB290CF519B2C406004C83CF /* UIKit.framework */; }; @@ -85,6 +86,8 @@ 98A03B4519F3514B00861ACA /* limelight-common.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = "limelight-common.xcodeproj"; path = "limelight-common-c/limelight-common.xcodeproj"; sourceTree = ""; }; 98A03B4E19F3598400861ACA /* VideoDecoderRenderer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = VideoDecoderRenderer.h; path = Limelight/VideoDecoderRenderer.h; sourceTree = SOURCE_ROOT; }; 98A03B4F19F3598400861ACA /* VideoDecoderRenderer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = VideoDecoderRenderer.m; path = Limelight/VideoDecoderRenderer.m; sourceTree = SOURCE_ROOT; }; + 98B175B219F5DAFC00DA2AED /* ControllerSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ControllerSupport.h; sourceTree = ""; }; + 98B175B319F5DAFC00DA2AED /* ControllerSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ControllerSupport.m; sourceTree = ""; }; FB290CEE19B2C406004C83CF /* Limelight.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Limelight.app; sourceTree = BUILT_PRODUCTS_DIR; }; FB290CF119B2C406004C83CF /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; FB290CF319B2C406004C83CF /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; @@ -337,6 +340,8 @@ FB8945EE19F5C3CD00339C8A /* Utils.m */, FB8945F019F5C76C00339C8A /* StreamConfiguration.h */, FB8945F119F5C76C00339C8A /* StreamConfiguration.m */, + 98B175B219F5DAFC00DA2AED /* ControllerSupport.h */, + 98B175B319F5DAFC00DA2AED /* ControllerSupport.m */, ); path = Limelight; sourceTree = ""; @@ -705,6 +710,7 @@ FB290D0419B2C406004C83CF /* AppDelegate.m in Sources */, FBAB29F619EDE0F800929691 /* Computer.m in Sources */, FB290D3A19B2C6E3004C83CF /* StreamFrameViewController.m in Sources */, + 98B175B419F5DAFC00DA2AED /* ControllerSupport.m in Sources */, FB290D0019B2C406004C83CF /* main.m in Sources */, FB8945F219F5C76C00339C8A /* StreamConfiguration.m in Sources */, FB290D3919B2C6E3004C83CF /* MainFrameViewController.m in Sources */, diff --git a/Limelight/ControllerSupport.h b/Limelight/ControllerSupport.h new file mode 100644 index 0000000..fffd79f --- /dev/null +++ b/Limelight/ControllerSupport.h @@ -0,0 +1,20 @@ +// +// ControllerSupport.h +// Limelight +// +// Created by Cameron Gutman on 10/20/14. +// Copyright (c) 2014 Limelight Stream. All rights reserved. +// + +#import + +@interface ControllerSupport : NSObject + +-(id) init; + +-(void) cleanup; + +@property (nonatomic, strong) id connectObserver; +@property (nonatomic, strong) id disconnectObserver; + +@end diff --git a/Limelight/ControllerSupport.m b/Limelight/ControllerSupport.m new file mode 100644 index 0000000..7dd583f --- /dev/null +++ b/Limelight/ControllerSupport.m @@ -0,0 +1,121 @@ +// +// ControllerSupport.m +// Limelight +// +// Created by Cameron Gutman on 10/20/14. +// Copyright (c) 2014 Limelight Stream. All rights reserved. +// + +#import "ControllerSupport.h" +#include "Limelight.h" + +@import GameController; + +@implementation ControllerSupport + +// UPDATE_BUTTON(flag, pressed) +#define UPDATE_BUTTON(x, y) (buttonFlags = \ + (y) ? (buttonFlags | (x)) : (buttonFlags & ~(x))) + +static NSLock *controllerStreamLock; + ++(void) registerControllerCallbacks +{ + for (int i = 0; i < [[GCController controllers] count]; i++) { + GCController *controller = [GCController controllers][i]; + + if (controller != NULL) { + NSLog(@"Controller connected!"); + if (controller.extendedGamepad != NULL) { + controller.extendedGamepad.valueChangedHandler = ^(GCExtendedGamepad *gamepad, GCControllerElement *element) { + short buttonFlags; + short leftStickX, leftStickY; + short rightStickX, rightStickY; + char leftTrigger, rightTrigger; + + UPDATE_BUTTON(A_FLAG, gamepad.buttonA.pressed); + UPDATE_BUTTON(B_FLAG, gamepad.buttonB.pressed); + UPDATE_BUTTON(X_FLAG, gamepad.buttonX.pressed); + UPDATE_BUTTON(Y_FLAG, gamepad.buttonY.pressed); + + UPDATE_BUTTON(UP_FLAG, gamepad.dpad.up.pressed); + UPDATE_BUTTON(DOWN_FLAG, gamepad.dpad.down.pressed); + UPDATE_BUTTON(LEFT_FLAG, gamepad.dpad.left.pressed); + UPDATE_BUTTON(RIGHT_FLAG, gamepad.dpad.right.pressed); + + UPDATE_BUTTON(LB_FLAG, gamepad.leftShoulder.pressed); + UPDATE_BUTTON(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; + + // We call LiSendControllerEvent while holding a lock to prevent + // multiple simultaneous calls since this function isn't thread safe. + [controllerStreamLock lock]; + LiSendControllerEvent(buttonFlags, leftTrigger, rightTrigger, + leftStickX, leftStickY, rightStickX, rightStickY); + [controllerStreamLock unlock]; + }; + } + else if (controller.gamepad != NULL) { + controller.gamepad.valueChangedHandler = ^(GCGamepad *gamepad, GCControllerElement *element) { + short buttonFlags; + + UPDATE_BUTTON(A_FLAG, gamepad.buttonA.pressed); + UPDATE_BUTTON(B_FLAG, gamepad.buttonB.pressed); + UPDATE_BUTTON(X_FLAG, gamepad.buttonX.pressed); + UPDATE_BUTTON(Y_FLAG, gamepad.buttonY.pressed); + + UPDATE_BUTTON(UP_FLAG, gamepad.dpad.up.pressed); + UPDATE_BUTTON(DOWN_FLAG, gamepad.dpad.down.pressed); + UPDATE_BUTTON(LEFT_FLAG, gamepad.dpad.left.pressed); + UPDATE_BUTTON(RIGHT_FLAG, gamepad.dpad.right.pressed); + + UPDATE_BUTTON(LB_FLAG, gamepad.leftShoulder.pressed); + UPDATE_BUTTON(RB_FLAG, gamepad.rightShoulder.pressed); + + // We call LiSendControllerEvent while holding a lock to prevent + // multiple simultaneous calls since this function isn't thread safe. + [controllerStreamLock lock]; + LiSendControllerEvent(buttonFlags, 0, 0, 0, 0, 0, 0); + [controllerStreamLock unlock]; + }; + } + } + } + +} + +-(id) init +{ + self = [super init]; + + if (controllerStreamLock == NULL) { + controllerStreamLock = [[NSLock alloc] init]; + } + + self.connectObserver = [[NSNotificationCenter defaultCenter] addObserverForName:GCControllerDidConnectNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { + [ControllerSupport registerControllerCallbacks]; + }]; + self.disconnectObserver = [[NSNotificationCenter defaultCenter] addObserverForName:GCControllerDidDisconnectNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { + NSLog(@"Controller disconnected!"); + }]; + + [ControllerSupport registerControllerCallbacks]; + + return self; +} + +-(void) cleanup +{ + [[NSNotificationCenter defaultCenter] removeObserver:self.connectObserver]; + [[NSNotificationCenter defaultCenter] removeObserver:self.disconnectObserver]; +} + +@end diff --git a/Limelight/StreamFrameViewController.m b/Limelight/StreamFrameViewController.m index 1f4d5d1..4a0fc60 100644 --- a/Limelight/StreamFrameViewController.m +++ b/Limelight/StreamFrameViewController.m @@ -11,12 +11,15 @@ #import "Connection.h" #import "VideoDecoderRenderer.h" #import "StreamManager.h" +#import "ControllerSupport.h" #include #include #include -@implementation StreamFrameViewController +@implementation StreamFrameViewController { + ControllerSupport *_controllerSupport; +} - (void)viewDidLoad { @@ -24,6 +27,8 @@ [UIApplication sharedApplication].idleTimerDisabled = YES; + _controllerSupport = [[ControllerSupport alloc] init]; + StreamManager* streamMan = [[StreamManager alloc] initWithHost:[MainFrameViewController getHost] renderView:self.view]; NSOperationQueue* opQueue = [[NSOperationQueue alloc] init]; [opQueue addOperation:streamMan];