mirror of
https://github.com/moonlight-stream/moonlight-ios.git
synced 2026-05-19 16:20:15 +00:00
Implement immediate controller arrival and battery events
This commit is contained in:
@@ -43,6 +43,10 @@ typedef struct {
|
|||||||
@property (nonatomic) NSTimer* _Nullable gyroTimer;
|
@property (nonatomic) NSTimer* _Nullable gyroTimer;
|
||||||
@property (nonatomic) GCRotationRate lastGyroSample;
|
@property (nonatomic) GCRotationRate lastGyroSample;
|
||||||
|
|
||||||
|
@property (nonatomic) NSTimer* _Nullable batteryTimer;
|
||||||
|
@property (nonatomic) GCDeviceBatteryState lastBatteryState;
|
||||||
|
@property (nonatomic) float lastBatteryLevel;
|
||||||
|
|
||||||
@property (nonatomic) BOOL reportedArrival;
|
@property (nonatomic) BOOL reportedArrival;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
@interface ControllerSupport : NSObject
|
@interface ControllerSupport : NSObject
|
||||||
|
|
||||||
-(id) initWithConfig:(StreamConfiguration*)streamConfig delegate:(id<ControllerSupportDelegate>)delegate;
|
-(id) initWithConfig:(StreamConfiguration*)streamConfig delegate:(id<ControllerSupportDelegate>)delegate;
|
||||||
|
-(void) connectionEstablished;
|
||||||
|
|
||||||
-(void) initAutoOnScreenControlMode:(OnScreenControls*)osc;
|
-(void) initAutoOnScreenControlMode:(OnScreenControls*)osc;
|
||||||
-(void) cleanup;
|
-(void) cleanup;
|
||||||
|
|||||||
@@ -336,16 +336,13 @@ static const double MOUSE_SPEED_DIVISOR = 1.25;
|
|||||||
exitRequested = YES;
|
exitRequested = YES;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Report the controller arrival to the host if we haven't done so yet
|
// Only send controller events if we successfully reported controller arrival
|
||||||
if (!controller.reportedArrival && controller.gamepad) {
|
if ([self reportControllerArrival:controller]) {
|
||||||
[self reportControllerArrival:controller.gamepad];
|
|
||||||
controller.reportedArrival = YES;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Player 1 is always present for OSC
|
// Player 1 is always present for OSC
|
||||||
LiSendMultiControllerEvent(_multiController ? controller.playerIndex : 0, [self getActiveGamepadMask],
|
LiSendMultiControllerEvent(_multiController ? controller.playerIndex : 0, [self getActiveGamepadMask],
|
||||||
controller.lastButtonFlags, controller.lastLeftTrigger, controller.lastRightTrigger, controller.lastLeftStickX, controller.lastLeftStickY, controller.lastRightStickX, controller.lastRightStickY);
|
controller.lastButtonFlags, controller.lastLeftTrigger, controller.lastRightTrigger, controller.lastLeftStickX, controller.lastLeftStickY, controller.lastRightStickX, controller.lastRightStickY);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
[_controllerStreamLock unlock];
|
[_controllerStreamLock unlock];
|
||||||
|
|
||||||
if (exitRequested) {
|
if (exitRequested) {
|
||||||
@@ -416,16 +413,67 @@ static const double MOUSE_SPEED_DIVISOR = 1.25;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
-(void) reportControllerArrival:(GCController*) controller
|
-(void) initializeControllerBattery:(Controller*) controller
|
||||||
{
|
{
|
||||||
if (controller == nil || controller.extendedGamepad == nil) {
|
if (@available(iOS 14.0, tvOS 14.0, *)) {
|
||||||
return;
|
if (controller.gamepad.battery) {
|
||||||
|
// Poll for updated battery status every 30 seconds
|
||||||
|
controller.batteryTimer = [NSTimer scheduledTimerWithTimeInterval:30 repeats:YES block:^(NSTimer *timer) {
|
||||||
|
if (controller.lastBatteryState != controller.gamepad.battery.batteryState ||
|
||||||
|
controller.lastBatteryLevel != controller.gamepad.battery.batteryLevel) {
|
||||||
|
uint8_t batteryState;
|
||||||
|
|
||||||
|
switch (controller.gamepad.battery.batteryState) {
|
||||||
|
case GCDeviceBatteryStateFull:
|
||||||
|
batteryState = LI_BATTERY_STATE_FULL;
|
||||||
|
break;
|
||||||
|
case GCDeviceBatteryStateCharging:
|
||||||
|
batteryState = LI_BATTERY_STATE_CHARGING;
|
||||||
|
break;
|
||||||
|
case GCDeviceBatteryStateDischarging:
|
||||||
|
batteryState = LI_BATTERY_STATE_DISCHARGING;
|
||||||
|
break;
|
||||||
|
case GCDeviceBatteryStateUnknown:
|
||||||
|
default:
|
||||||
|
batteryState = LI_BATTERY_STATE_UNKNOWN;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
LiSendControllerBatteryEvent(controller.playerIndex, batteryState, (uint8_t)(controller.gamepad.battery.batteryLevel * 100));
|
||||||
|
|
||||||
|
controller.lastBatteryState = controller.gamepad.battery.batteryState;
|
||||||
|
controller.lastBatteryLevel = controller.gamepad.battery.batteryLevel;
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
|
||||||
|
// Fire the timer immediately to send the initial battery state
|
||||||
|
[controller.batteryTimer fire];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
-(void) cleanupControllerBattery:(Controller*) controller
|
||||||
|
{
|
||||||
|
if (@available(iOS 14.0, tvOS 14.0, *)) {
|
||||||
|
[controller.batteryTimer invalidate];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
-(BOOL) reportControllerArrival:(Controller*) limeController
|
||||||
|
{
|
||||||
|
// Only report arrival once
|
||||||
|
if (limeController.reportedArrival) {
|
||||||
|
return YES;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t type = LI_CTYPE_UNKNOWN;
|
uint8_t type = LI_CTYPE_UNKNOWN;
|
||||||
uint16_t capabilities = 0;
|
uint16_t capabilities = 0;
|
||||||
uint32_t supportedButtonFlags = 0;
|
uint32_t supportedButtonFlags = 0;
|
||||||
|
|
||||||
|
GCController *controller = limeController.gamepad;
|
||||||
|
if (controller) {
|
||||||
|
// This is a physical controller with a corresponding GCController object
|
||||||
|
|
||||||
// Start is always present
|
// Start is always present
|
||||||
supportedButtonFlags |= PLAY_FLAG;
|
supportedButtonFlags |= PLAY_FLAG;
|
||||||
|
|
||||||
@@ -536,9 +584,41 @@ static const double MOUSE_SPEED_DIVISOR = 1.25;
|
|||||||
capabilities |= LI_CCAP_RGB_LED;
|
capabilities |= LI_CCAP_RGB_LED;
|
||||||
}
|
}
|
||||||
|
|
||||||
LiSendControllerArrivalEvent(controller.playerIndex, [self getActiveGamepadMask], type, supportedButtonFlags, capabilities);
|
// Detect battery support
|
||||||
|
if (controller.battery) {
|
||||||
|
capabilities |= LI_CCAP_BATTERY_STATE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
// This is a virtual controller corresponding to our OSC
|
||||||
|
|
||||||
|
// TODO: Support various layouts and button labels on the OSC
|
||||||
|
type = LI_CTYPE_XBOX;
|
||||||
|
capabilities = 0;
|
||||||
|
supportedButtonFlags =
|
||||||
|
PLAY_FLAG | BACK_FLAG | UP_FLAG | DOWN_FLAG | LEFT_FLAG | RIGHT_FLAG |
|
||||||
|
LB_FLAG | RB_FLAG | LS_CLK_FLAG | RS_CLK_FLAG | A_FLAG | B_FLAG | X_FLAG | Y_FLAG;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Report the new controller to the host
|
||||||
|
// NB: This will fail if the connection hasn't been fully established yet
|
||||||
|
// and we will try again later.
|
||||||
|
if (LiSendControllerArrivalEvent(controller.playerIndex,
|
||||||
|
[self getActiveGamepadMask],
|
||||||
|
type,
|
||||||
|
supportedButtonFlags,
|
||||||
|
capabilities) != 0) {
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Begin polling for battery status
|
||||||
|
[self initializeControllerBattery:limeController];
|
||||||
|
|
||||||
|
// Remember that we've reported arrival already
|
||||||
|
limeController.reportedArrival = YES;
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
|
||||||
-(void) handleControllerTouchpad:(Controller*)controller touch:(GCControllerDirectionPad*)touch index:(int)index
|
-(void) handleControllerTouchpad:(Controller*)controller touch:(GCControllerDirectionPad*)touch index:(int)index
|
||||||
{
|
{
|
||||||
@@ -874,7 +954,7 @@ static const double MOUSE_SPEED_DIVISOR = 1.25;
|
|||||||
[self updateAutoOnScreenControlMode];
|
[self updateAutoOnScreenControlMode];
|
||||||
}
|
}
|
||||||
|
|
||||||
-(void) assignController:(GCController*)controller {
|
-(Controller*) assignController:(GCController*)controller {
|
||||||
for (int i = 0; i < 4; i++) {
|
for (int i = 0; i < 4; i++) {
|
||||||
if (!(_controllerNumbers & (1 << i))) {
|
if (!(_controllerNumbers & (1 << i))) {
|
||||||
_controllerNumbers |= (1 << i);
|
_controllerNumbers |= (1 << i);
|
||||||
@@ -915,9 +995,11 @@ static const double MOUSE_SPEED_DIVISOR = 1.25;
|
|||||||
[_controllers setObject:limeController forKey:[NSNumber numberWithInteger:controller.playerIndex]];
|
[_controllers setObject:limeController forKey:[NSNumber numberWithInteger:controller.playerIndex]];
|
||||||
|
|
||||||
Log(LOG_I, @"Assigning controller index: %d", i);
|
Log(LOG_I, @"Assigning controller index: %d", i);
|
||||||
break;
|
return limeController;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
-(Controller*) getOscController {
|
-(Controller*) getOscController {
|
||||||
@@ -1024,19 +1106,20 @@ static const double MOUSE_SPEED_DIVISOR = 1.25;
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
[self assignController:controller];
|
Controller* limeController = [self assignController:controller];
|
||||||
|
if (limeController) {
|
||||||
// Register callbacks on the new controller
|
// Register callbacks on the new controller
|
||||||
[self registerControllerCallbacks:controller];
|
[self registerControllerCallbacks:controller];
|
||||||
|
|
||||||
// Report the controller arrival to the host
|
// Report the controller arrival to the host if we're connected
|
||||||
[self reportControllerArrival:controller];
|
[self reportControllerArrival:limeController];
|
||||||
|
|
||||||
// Re-evaluate the on-screen control mode
|
// Re-evaluate the on-screen control mode
|
||||||
[self updateAutoOnScreenControlMode];
|
[self updateAutoOnScreenControlMode];
|
||||||
|
|
||||||
// Notify the delegate
|
// Notify the delegate
|
||||||
[self->_delegate gamepadPresenceChanged];
|
[self->_delegate gamepadPresenceChanged];
|
||||||
|
}
|
||||||
}];
|
}];
|
||||||
_controllerDisconnectObserver = [[NSNotificationCenter defaultCenter] addObserverForName:GCControllerDidDisconnectNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
|
_controllerDisconnectObserver = [[NSNotificationCenter defaultCenter] addObserverForName:GCControllerDidDisconnectNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
|
||||||
Log(LOG_I, @"Controller disconnected!");
|
Log(LOG_I, @"Controller disconnected!");
|
||||||
@@ -1052,15 +1135,18 @@ static const double MOUSE_SPEED_DIVISOR = 1.25;
|
|||||||
self->_controllerNumbers &= ~(1 << controller.playerIndex);
|
self->_controllerNumbers &= ~(1 << controller.playerIndex);
|
||||||
Log(LOG_I, @"Unassigning controller index: %ld", (long)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]];
|
Controller* limeController = [self->_controllers objectForKey:[NSNumber numberWithInteger:controller.playerIndex]];
|
||||||
|
if (limeController) {
|
||||||
// Stop haptics on this controller
|
// Stop haptics on this controller
|
||||||
[self cleanupControllerHaptics:limeController];
|
[self cleanupControllerHaptics:limeController];
|
||||||
|
|
||||||
// Stop motion reports on this controller
|
// Stop motion reports on this controller
|
||||||
[self cleanupControllerMotion:limeController];
|
[self cleanupControllerMotion:limeController];
|
||||||
|
|
||||||
|
// Stop battery reports on this controller
|
||||||
|
[self cleanupControllerBattery:limeController];
|
||||||
|
|
||||||
|
// Unset the GCController on this object (in case it is the OSC, which will persist)
|
||||||
limeController.gamepad = nil;
|
limeController.gamepad = nil;
|
||||||
|
|
||||||
// Inform the server of the updated active gamepads before removing this controller
|
// Inform the server of the updated active gamepads before removing this controller
|
||||||
@@ -1072,6 +1158,7 @@ static const double MOUSE_SPEED_DIVISOR = 1.25;
|
|||||||
|
|
||||||
// Notify the delegate
|
// Notify the delegate
|
||||||
[self->_delegate gamepadPresenceChanged];
|
[self->_delegate gamepadPresenceChanged];
|
||||||
|
}
|
||||||
}];
|
}];
|
||||||
|
|
||||||
if (@available(iOS 14.0, tvOS 14.0, *)) {
|
if (@available(iOS 14.0, tvOS 14.0, *)) {
|
||||||
@@ -1120,6 +1207,14 @@ static const double MOUSE_SPEED_DIVISOR = 1.25;
|
|||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
-(void) connectionEstablished
|
||||||
|
{
|
||||||
|
for (Controller* controller in [_controllers allValues]) {
|
||||||
|
// Report the controller arrival to the host if we haven't done so yet
|
||||||
|
[self reportControllerArrival:controller];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
-(void) cleanup
|
-(void) cleanup
|
||||||
{
|
{
|
||||||
[[NSNotificationCenter defaultCenter] removeObserver:_controllerConnectObserver];
|
[[NSNotificationCenter defaultCenter] removeObserver:_controllerConnectObserver];
|
||||||
@@ -1141,6 +1236,7 @@ static const double MOUSE_SPEED_DIVISOR = 1.25;
|
|||||||
for (Controller* controller in [_controllers allValues]) {
|
for (Controller* controller in [_controllers allValues]) {
|
||||||
[self cleanupControllerHaptics:controller];
|
[self cleanupControllerHaptics:controller];
|
||||||
[self cleanupControllerMotion:controller];
|
[self cleanupControllerMotion:controller];
|
||||||
|
[self cleanupControllerBattery:controller];
|
||||||
}
|
}
|
||||||
[_controllers removeAllObjects];
|
[_controllers removeAllObjects];
|
||||||
|
|
||||||
|
|||||||
@@ -377,6 +377,8 @@
|
|||||||
|
|
||||||
[self->_streamView showOnScreenControls];
|
[self->_streamView showOnScreenControls];
|
||||||
|
|
||||||
|
[self->_controllerSupport connectionEstablished];
|
||||||
|
|
||||||
if (self->_settings.statsOverlay) {
|
if (self->_settings.statsOverlay) {
|
||||||
self->_statsUpdateTimer = [NSTimer scheduledTimerWithTimeInterval:1.0f
|
self->_statsUpdateTimer = [NSTimer scheduledTimerWithTimeInterval:1.0f
|
||||||
target:self
|
target:self
|
||||||
|
|||||||
Reference in New Issue
Block a user