From 9da1121cdb9169845d78f55fc492f2fb928d99d5 Mon Sep 17 00:00:00 2001 From: True Zhuanjia Date: Sun, 16 Aug 2026 15:29:49 +0800 Subject: [PATCH] dual sense adaptive trigger implementation working with Sunshine-Foundation --- VoidLink/Input/ControllerSupport.h | 3 + VoidLink/Input/ControllerSupport.m | 118 +++++++++++++++++- VoidLink/Stream/Connection.m | 13 ++ VoidLink/Stream/ConnectionCallbacks.h | 3 + VoidLink/Utility/FrameQueue.h | 14 +++ VoidLink/Utility/FrameQueue.m | 13 ++ .../StreamFrameViewController.m | 11 ++ 7 files changed, 171 insertions(+), 4 deletions(-) diff --git a/VoidLink/Input/ControllerSupport.h b/VoidLink/Input/ControllerSupport.h index c5d83b6c..b97103d2 100644 --- a/VoidLink/Input/ControllerSupport.h +++ b/VoidLink/Input/ControllerSupport.h @@ -52,6 +52,9 @@ -(void) rumbleTriggers:(uint16_t)controllerNumber leftTrigger:(uint16_t)leftTrigger rightTrigger:(uint16_t)rightTrigger; -(void) setMotionEventState:(uint16_t)controllerNumber motionType:(uint8_t)motionType reportRateHz:(uint16_t)reportRateHz; -(void) setControllerLed:(uint16_t)controllerNumber r:(uint8_t)r g:(uint8_t)g b:(uint8_t)b; +-(void) setAdaptiveTriggers:(uint16_t)controllerNumber eventFlags:(uint8_t)eventFlags + typeLeft:(uint8_t)typeLeft typeRight:(uint8_t)typeRight + left:(const uint8_t*)left right:(const uint8_t*)right; -(void) updateTimerStateForOsc; -(uint16_t) getActiveGamepadMask; diff --git a/VoidLink/Input/ControllerSupport.m b/VoidLink/Input/ControllerSupport.m index 5f426a5c..8f87e5cc 100644 --- a/VoidLink/Input/ControllerSupport.m +++ b/VoidLink/Input/ControllerSupport.m @@ -16,6 +16,7 @@ #import "DataManager.h" #include "Limelight.h" +#include @import GameController; #if !TARGET_OS_TV @@ -26,6 +27,61 @@ static const double MOUSE_SPEED_DIVISOR = 1.25; static __weak ControllerSupport *VLSharedControllerSupport = nil; +typedef struct _VL_ADAPTIVE_TRIGGER_EFFECT { + uint8_t type; + float parameter0; + float parameter1; + float parameter2; +} VL_ADAPTIVE_TRIGGER_EFFECT; + +static VL_ADAPTIVE_TRIGGER_EFFECT DecodeAdaptiveTriggerEffect(uint8_t type, const uint8_t* payload) +{ + const float byteScale = 1.0f / 255.0f; + VL_ADAPTIVE_TRIGGER_EFFECT effect = { + .type = type, + .parameter0 = payload[0] * byteScale, + .parameter1 = payload[1] * byteScale, + .parameter2 = payload[2] * byteScale, + }; + return effect; +} + +static void ApplyAdaptiveTriggerEffect(GCDualSenseAdaptiveTrigger* trigger, + VL_ADAPTIVE_TRIGGER_EFFECT effect) + API_AVAILABLE(ios(14.5), tvos(14.5)); + +static void ApplyAdaptiveTriggerEffect(GCDualSenseAdaptiveTrigger* trigger, + VL_ADAPTIVE_TRIGGER_EFFECT effect) +{ + switch (effect.type) { + case 0x00: + [trigger setModeOff]; + break; + case 0x01: + [trigger setModeFeedbackWithStartPosition:effect.parameter0 + resistiveStrength:effect.parameter1]; + break; + case 0x02: + if (effect.parameter1 > effect.parameter0) { + [trigger setModeWeaponWithStartPosition:effect.parameter0 + endPosition:effect.parameter1 + resistiveStrength:effect.parameter2]; + } + else { + // Log(LOG_W, @"Ignoring invalid adaptive weapon effect: start=%.3f end=%.3f", effect.parameter0, effect.parameter1); + } + break; + case 0x06: + [trigger setModeVibrationWithStartPosition:effect.parameter0 + amplitude:effect.parameter1 + frequency:effect.parameter2]; + break; + default: + // Log(LOG_W, @"Ignoring unsupported adaptive trigger effect type: 0x%02X", effect.type); + break; + } +} + @interface ControllerSupport() @property (assign,nonatomic) bool shallDisableGyroHotSwitch; @@ -455,6 +511,55 @@ static __weak ControllerSupport *VLSharedControllerSupport = nil; } } +-(void) setAdaptiveTriggers:(uint16_t)controllerNumber eventFlags:(uint8_t)eventFlags + typeLeft:(uint8_t)typeLeft typeRight:(uint8_t)typeRight + left:(const uint8_t*)left right:(const uint8_t*)right { + char leftPayload[DS_EFFECT_PAYLOAD_SIZE * 3] = {0}; + char rightPayload[DS_EFFECT_PAYLOAD_SIZE * 3] = {0}; + + for (int i = 0; i < DS_EFFECT_PAYLOAD_SIZE; i++) { + snprintf(leftPayload + (i * 3), sizeof(leftPayload) - (i * 3), + i == DS_EFFECT_PAYLOAD_SIZE - 1 ? "%02X" : "%02X ", left[i]); + snprintf(rightPayload + (i * 3), sizeof(rightPayload) - (i * 3), + i == DS_EFFECT_PAYLOAD_SIZE - 1 ? "%02X" : "%02X ", right[i]); + } + + /* + Log(LOG_I, @"Adaptive trigger: controller=%u flags=0x%02X " + "leftType=0x%02X left=[%s] rightType=0x%02X right=[%s]", + controllerNumber, eventFlags, + typeLeft, leftPayload, typeRight, rightPayload); + */ + + VL_ADAPTIVE_TRIGGER_EFFECT leftEffect = DecodeAdaptiveTriggerEffect(typeLeft, left); + VL_ADAPTIVE_TRIGGER_EFFECT rightEffect = DecodeAdaptiveTriggerEffect(typeRight, right); + bool applyLeft = (eventFlags & DS_EFFECT_LEFT_TRIGGER) != 0; + bool applyRight = (eventFlags & DS_EFFECT_RIGHT_TRIGGER) != 0; + + dispatch_async(dispatch_get_main_queue(), ^{ + if (@available(iOS 14.5, tvOS 14.5, *)) { + VoidController* controller = [self->_voidControllers objectForKey:@(controllerNumber)]; + if (controller == nil) { + Log(LOG_W, @"Ignoring adaptive trigger effect for missing gamepad %u", controllerNumber); + return; + } + + if (![controller.gamepad.extendedGamepad isKindOfClass:[GCDualSenseGamepad class]]) { + Log(LOG_W, @"Ignoring adaptive trigger effect for non-DualSense gamepad %u", controllerNumber); + return; + } + + GCDualSenseGamepad* dualSense = (GCDualSenseGamepad*)controller.gamepad.extendedGamepad; + if (applyLeft) { + ApplyAdaptiveTriggerEffect(dualSense.leftTrigger, leftEffect); + } + if (applyRight) { + ApplyAdaptiveTriggerEffect(dualSense.rightTrigger, rightEffect); + } + } + }); +} + -(void) updateLeftStick:(VoidController*)controller x:(short)x y:(short)y { @synchronized(controller) { @@ -691,6 +796,14 @@ static __weak ControllerSupport *VLSharedControllerSupport = nil; -(void) cleanupControllerHaptics:(VoidController*) controller { + if (@available(iOS 14.5, tvOS 14.5, *)) { + if ([controller.gamepad.extendedGamepad isKindOfClass:[GCDualSenseGamepad class]]) { + GCDualSenseGamepad* dualSense = (GCDualSenseGamepad*)controller.gamepad.extendedGamepad; + [dualSense.leftTrigger setModeOff]; + [dualSense.rightTrigger setModeOff]; + } + } + [controller.lowFreqMotor cleanup]; [controller.highFreqMotor cleanup]; [controller.leftTriggerMotor cleanup]; @@ -854,7 +967,6 @@ static __weak ControllerSupport *VLSharedControllerSupport = nil; capabilities |= LI_CCAP_TOUCHPAD; } - // LI_CTYPE_UNKNOWN for option "Both" if(voidController.playerIndex == 0){ type = _streamConfig.emulatedControllerType == LI_CTYPE_UNKNOWN ? LI_CTYPE_PS : _streamConfig.emulatedControllerType; @@ -878,9 +990,7 @@ static __weak ControllerSupport *VLSharedControllerSupport = nil; } } } - - - + // Detect supported haptics localities if (controller.haptics) { if ([controller.haptics.supportedLocalities containsObject:GCHapticsLocalityHandles]) { diff --git a/VoidLink/Stream/Connection.m b/VoidLink/Stream/Connection.m index a22b21fc..6949cbf5 100644 --- a/VoidLink/Stream/Connection.m +++ b/VoidLink/Stream/Connection.m @@ -576,6 +576,18 @@ void ClSetControllerLED(uint16_t controllerNumber, uint8_t r, uint8_t g, uint8_t [_callbacks setControllerLed:controllerNumber r:r g:g b:b]; } +void ClSetAdaptiveTriggers(uint16_t controllerNumber, uint8_t eventFlags, + uint8_t typeLeft, uint8_t typeRight, + uint8_t* left, uint8_t* right) +{ + [_callbacks setAdaptiveTriggers:controllerNumber + eventFlags:eventFlags + typeLeft:typeLeft + typeRight:typeRight + left:left + right:right]; +} + -(void) terminate { // Interrupt any action blocking LiStartConnection(). This is @@ -737,6 +749,7 @@ void ClSetControllerLED(uint16_t controllerNumber, uint8_t r, uint8_t g, uint8_t _clCallbacks.rumbleTriggers = ClRumbleTriggers; _clCallbacks.setMotionEventState = ClSetMotionEventState; _clCallbacks.setControllerLED = ClSetControllerLED; + _clCallbacks.setAdaptiveTriggers = ClSetAdaptiveTriggers; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleAudioSessionInterruption:) diff --git a/VoidLink/Stream/ConnectionCallbacks.h b/VoidLink/Stream/ConnectionCallbacks.h index 3cfc8e8f..605498d1 100644 --- a/VoidLink/Stream/ConnectionCallbacks.h +++ b/VoidLink/Stream/ConnectionCallbacks.h @@ -22,6 +22,9 @@ - (void) rumbleTriggers:(uint16_t)controllerNumber leftTrigger:(uint16_t)leftTrigger rightTrigger:(uint16_t)rightTrigger; - (void) setMotionEventState:(uint16_t)controllerNumber motionType:(uint8_t)motionType reportRateHz:(uint16_t)reportRateHz; - (void) setControllerLed:(uint16_t)controllerNumber r:(uint8_t)r g:(uint8_t)g b:(uint8_t)b; +- (void) setAdaptiveTriggers:(uint16_t)controllerNumber eventFlags:(uint8_t)eventFlags + typeLeft:(uint8_t)typeLeft typeRight:(uint8_t)typeRight + left:(const uint8_t*)left right:(const uint8_t*)right; - (void) videoContentShown; @end diff --git a/VoidLink/Utility/FrameQueue.h b/VoidLink/Utility/FrameQueue.h index d70d8a60..c8d37f3e 100644 --- a/VoidLink/Utility/FrameQueue.h +++ b/VoidLink/Utility/FrameQueue.h @@ -1,3 +1,17 @@ +// +// FrameQueue.h +// Moonlight +// +// Created by Andy Grundman. +// Ported to VoidLink by Acaki. +// Copyright (c) 2025 Moonlight Stream. All rights reserved. +// +// Modified by True砖家 since 2026/8/12. +// Copyright © 2026 True砖家 on Bilibili. All rights reserved. +// +// + + #import #import diff --git a/VoidLink/Utility/FrameQueue.m b/VoidLink/Utility/FrameQueue.m index d47177e6..03348849 100644 --- a/VoidLink/Utility/FrameQueue.m +++ b/VoidLink/Utility/FrameQueue.m @@ -1,3 +1,16 @@ +// +// FrameQueue.m +// Moonlight +// +// Created by Andy Grundman. +// Ported to VoidLink by Acaki. +// Copyright (c) 2025 Moonlight Stream. All rights reserved. +// +// Modified by True砖家 since 2026/8/6. +// Copyright © 2026 True砖家 on Bilibili. All rights reserved. +// + + @import AVFoundation; @import VideoToolbox; diff --git a/VoidLink/ViewControllers/StreamFrameViewController.m b/VoidLink/ViewControllers/StreamFrameViewController.m index d51b0aec..03925d18 100644 --- a/VoidLink/ViewControllers/StreamFrameViewController.m +++ b/VoidLink/ViewControllers/StreamFrameViewController.m @@ -1946,6 +1946,17 @@ static __weak StreamFrameViewController *VLSharedStreamFrameViewController = nil [_controllerSupport setControllerLed:controllerNumber r:r g:g b:b]; } +- (void) setAdaptiveTriggers:(uint16_t)controllerNumber eventFlags:(uint8_t)eventFlags + typeLeft:(uint8_t)typeLeft typeRight:(uint8_t)typeRight + left:(const uint8_t*)left right:(const uint8_t*)right { + [_controllerSupport setAdaptiveTriggers:controllerNumber + eventFlags:eventFlags + typeLeft:typeLeft + typeRight:typeRight + left:left + right:right]; +} + - (void)connectionStatusUpdate:(int)status { Log(LOG_W, @"Connection status update: %d", status);