dualSense emulation beta ready (works with Sunshine-Foundation)

This commit is contained in:
True Zhuanjia
2026-08-21 18:01:39 +08:00
parent e63e5f2120
commit 089515443d
22 changed files with 1290 additions and 510 deletions
+65 -66
View File
@@ -264,7 +264,7 @@
</constraints>
</stackView>
<stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" axis="vertical" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="LGh-Lc-Odp" userLabel="emulatedControllerTypeStack">
<rect key="frame" x="440" y="973" width="380" height="56.5"/>
<rect key="frame" x="440" y="924" width="380" height="56.5"/>
<subviews>
<label verifyAmbiguity="off" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Emulated Controller Type" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="JTl-un-QJJ" userLabel="emulatedControllerTypeLabel">
<rect key="frame" x="0.0" y="0.0" width="380" height="20.5"/>
@@ -272,12 +272,13 @@
<color key="textColor" red="0.93902439019999995" green="0.9625305918" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<segmentedControl verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="JaY-XA-1V6" userLabel="emulatedControllerTypeSelector">
<segmentedControl verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" apportionsSegmentWidthsByContent="YES" segmentControlStyle="plain" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="JaY-XA-1V6" userLabel="emulatedControllerTypeSelector">
<rect key="frame" x="0.0" y="25.5" width="380" height="32"/>
<segments>
<segment title="Xbox 360"/>
<segment title="DS4"/>
<segment title="Both"/>
<segment title="PS"/>
<segment title="PS (Enhanced Haptics)"/>
<segment title="Xbox + PS"/>
</segments>
<color key="tintColor" red="0.6716768742" green="0.61711704730000005" blue="0.99902987480000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="selectedSegmentTintColor" systemColor="linkColor"/>
@@ -414,37 +415,37 @@
<stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" axis="vertical" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="FIp-Lv-StK" userLabel="interpolationLevel Stack">
<rect key="frame" x="440" y="1350" width="380" height="56.5"/>
<subviews>
<label verifyAmbiguity="off" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Interpolation Resolution" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="FIp-Lv-Lbl">
<rect key="frame" x="0.0" y="0.0" width="380" height="20.5"/>
<label verifyAmbiguity="off" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Interpolation Resolution" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="FIp-Lv-Lbl">
<rect key="frame" x="0.0" y="0.0" width="380" height="17.5"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.93902439019999995" green="0.9625305918" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<slider verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" value="1" minValue="0.0" maxValue="1" translatesAutoresizingMaskIntoConstraints="NO" id="FIp-Lv-Sel" userLabel="Interpolation Level Slider">
<rect key="frame" x="0.0" y="25.5" width="380" height="31"/>
<slider verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" value="1" minValue="0.0" maxValue="1" translatesAutoresizingMaskIntoConstraints="NO" id="FIp-Lv-Sel" userLabel="Interpolation Level Slider">
<rect key="frame" x="0.0" y="22.5" width="380" height="34"/>
</slider>
</subviews>
<constraints>
<constraint firstAttribute="width" constant="380" id="FIp-Lv-Wid"/>
<constraint firstAttribute="height" constant="56.5" id="FIp-Lv-Hgt"/>
<constraint firstAttribute="width" constant="380" id="FIp-Lv-Wid"/>
</constraints>
</stackView>
<stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" axis="vertical" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="FIp-Sc-StK" userLabel="streamDimensionScale Stack">
<rect key="frame" x="440" y="1411" width="380" height="56.5"/>
<subviews>
<label verifyAmbiguity="off" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Scaled Stream Resolution" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="FIp-Sc-Lbl">
<rect key="frame" x="0.0" y="0.0" width="380" height="20.5"/>
<label verifyAmbiguity="off" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Scaled Stream Resolution" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="FIp-Sc-Lbl">
<rect key="frame" x="0.0" y="0.0" width="380" height="17.5"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.93902439019999995" green="0.9625305918" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<slider verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" value="1" minValue="0.0" maxValue="1" translatesAutoresizingMaskIntoConstraints="NO" id="FIp-Sc-Sld" userLabel="Stream Dimension Scale Slider">
<rect key="frame" x="0.0" y="25.5" width="380" height="31"/>
<slider verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" value="1" minValue="0.0" maxValue="1" translatesAutoresizingMaskIntoConstraints="NO" id="FIp-Sc-Sld" userLabel="Stream Dimension Scale Slider">
<rect key="frame" x="0.0" y="22.5" width="380" height="34"/>
</slider>
</subviews>
<constraints>
<constraint firstAttribute="width" constant="380" id="FIp-Sc-Wid"/>
<constraint firstAttribute="height" constant="56.5" id="FIp-Sc-Hgt"/>
<constraint firstAttribute="width" constant="380" id="FIp-Sc-Wid"/>
</constraints>
</stackView>
<stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="hcl-DT-peO" userLabel="redirectMicStack">
@@ -1078,48 +1079,8 @@
<constraint firstAttribute="width" constant="380" id="qOk-ee-a8A"/>
</constraints>
</stackView>
<stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="A31-WC-As7">
<rect key="frame" x="441" y="1363" width="280" height="31"/>
<subviews>
<label verifyAmbiguity="off" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Enable Timebase" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.64999997615814209" adjustsLetterSpacingToFitWidth="YES" adjustsFontForContentSizeCategory="YES" translatesAutoresizingMaskIntoConstraints="NO" id="cpg-mq-kYj" userLabel="uiLabel">
<rect key="frame" x="0.0" y="0.0" width="130" height="31"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="StG-nI-WpK">
<rect key="frame" x="150" y="0.0" width="132" height="31"/>
<constraints>
<constraint firstAttribute="width" constant="130" id="lFV-eP-bfD"/>
</constraints>
</switch>
</subviews>
<constraints>
<constraint firstAttribute="height" constant="31" id="seO-0F-yIw"/>
</constraints>
</stackView>
<stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="e0Q-XV-ydj">
<rect key="frame" x="441" y="1402" width="322" height="31"/>
<subviews>
<label verifyAmbiguity="off" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Async Frame Dequeue" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.64999997615814209" adjustsLetterSpacingToFitWidth="YES" adjustsFontForContentSizeCategory="YES" translatesAutoresizingMaskIntoConstraints="NO" id="X0u-7f-F86" userLabel="uiLabel">
<rect key="frame" x="0.0" y="0.0" width="172" height="31"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="auR-cw-77D">
<rect key="frame" x="192" y="0.0" width="132" height="31"/>
<constraints>
<constraint firstAttribute="width" constant="130" id="mZn-6A-4OO"/>
</constraints>
</switch>
</subviews>
<constraints>
<constraint firstAttribute="height" constant="31" id="UK8-ck-1zc"/>
</constraints>
</stackView>
<stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="ydX-NT-gG0">
<rect key="frame" x="441" y="1441" width="383" height="31"/>
<rect key="frame" x="437" y="1480" width="383" height="31"/>
<subviews>
<label verifyAmbiguity="off" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="SDR Performance Workaround" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.64999997615814209" adjustsLetterSpacingToFitWidth="YES" adjustsFontForContentSizeCategory="YES" translatesAutoresizingMaskIntoConstraints="NO" id="WWl-77-twB" userLabel="uiLabel">
<rect key="frame" x="0.0" y="0.0" width="233" height="31"/>
@@ -2280,6 +2241,44 @@
<constraint firstAttribute="height" constant="55.5" id="f4J-WQ-LBI"/>
</constraints>
</stackView>
<stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="e0Q-XV-ydj">
<rect key="frame" x="852" y="1300" width="322" height="31"/>
<subviews>
<label verifyAmbiguity="off" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Async Frame Dequeue" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.64999997615814209" adjustsLetterSpacingToFitWidth="YES" adjustsFontForContentSizeCategory="YES" translatesAutoresizingMaskIntoConstraints="NO" id="X0u-7f-F86" userLabel="uiLabel">
<rect key="frame" x="0.0" y="0.0" width="172" height="31"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="auR-cw-77D">
<rect key="frame" x="192" y="0.0" width="132" height="31"/>
<constraints>
<constraint firstAttribute="width" constant="130" id="mZn-6A-4OO"/>
</constraints>
</switch>
</subviews>
<constraints>
<constraint firstAttribute="height" constant="31" id="UK8-ck-1zc"/>
</constraints>
</stackView>
<stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" axis="vertical" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="xrc-Fe-ETh" userLabel="dualSenseTransientStack">
<rect key="frame" x="440" y="998" width="380" height="56.5"/>
<subviews>
<label verifyAmbiguity="off" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="DualSense Haptic Intensity" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="UpM-yi-yc9">
<rect key="frame" x="0.0" y="0.0" width="380" height="17.5"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.93902439019999995" green="0.9625305918" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<slider verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" minValue="0.0" maxValue="2" translatesAutoresizingMaskIntoConstraints="NO" id="ZOE-dg-g4w" userLabel="dualSenseTransientSlider">
<rect key="frame" x="0.0" y="22.5" width="380" height="34"/>
</slider>
</subviews>
<constraints>
<constraint firstAttribute="height" constant="56.5" id="aRM-XQ-Xxd"/>
<constraint firstAttribute="width" constant="380" id="k8R-Co-bxO"/>
</constraints>
</stackView>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</view>
@@ -2324,12 +2323,14 @@
<outlet property="disablePencilSlideGestureSwitch" destination="lcb-Iq-B5C" id="NZY-fW-4Yd"/>
<outlet property="doubleTapShortcutStack" destination="prr-Do-Nna" id="sK3-va-U5W"/>
<outlet property="doubleTapShortcutSwitch" destination="cw7-Jc-7bZ" id="bRy-9x-0Tl"/>
<outlet property="dualSenseTransientSlider" destination="ZOE-dg-g4w" id="J8t-pc-pBo"/>
<outlet property="dualSenseTransientStack" destination="xrc-Fe-ETh" id="bAL-at-fHy"/>
<outlet property="duckOtherAppStack" destination="gBG-8F-6F8" id="T9P-ys-pHB"/>
<outlet property="duckOtherAppSwitch" destination="YEv-1B-97I" id="o3Q-yt-74a"/>
<outlet property="edgeSlidingSensitivitySlider" destination="41s-BD-8rZ" id="ytk-Y5-Alq"/>
<outlet property="edgeSlidingSensitivityStack" destination="CVS-fB-e0q" id="tGR-K2-Jwq"/>
<outlet property="emulatedControllerTypeSelector" destination="JaY-XA-1V6" id="aU2-ps-esq"/>
<outlet property="emulatedControllerTypeStack" destination="LGh-Lc-Odp" id="Fny-rC-9zP"/>
<outlet property="emulatedControllerTypeStack" destination="LGh-Lc-Odp" id="MHo-66-ltO"/>
<outlet property="enableGraphsSwitch" destination="mhH-ZA-Kqg" id="R0X-hj-NgM"/>
<outlet property="enableOswForNativeTouchSwitch" destination="Zmq-Kv-Nmp" id="f6j-0V-ldT"/>
<outlet property="enableOswSwitchStack" destination="SkS-ft-peB" id="n3M-EK-U74"/>
@@ -2338,15 +2339,9 @@
<outlet property="fpsStack" destination="TuR-H8-e2H" id="zGQ-gR-P1i"/>
<outlet property="framePacingModeSelector" destination="2Dp-Qg-chi" id="h8A-YF-SZP"/>
<outlet property="framePacingStack" destination="oZG-5Y-pw5" id="vGd-hD-zTr"/>
<outlet property="interpolationLevelSlider" destination="FIp-Lv-Sel" id="FIp-Out-Sel"/>
<outlet property="interpolationLevelStack" destination="FIp-Lv-StK" id="FIp-Out-StK"/>
<outlet property="streamDimensionScaleSlider" destination="FIp-Sc-Sld" id="FIp-Out-ScS"/>
<outlet property="streamDimensionScaleStack" destination="FIp-Sc-StK" id="FIp-Out-ScK"/>
<outlet property="frameQueueSizeLabel" destination="Ce3-FM-vIh" id="90D-Tq-OGd"/>
<outlet property="frameQueueSizeSlider" destination="oZa-8Y-icz" id="Jo1-Fx-UMw"/>
<outlet property="frameQueueSizeStack" destination="MML-c1-D5j" id="82B-r7-Zbh"/>
<outlet property="frameTimebaseStack" destination="A31-WC-As7" id="vcp-SE-PuI"/>
<outlet property="frameTimebaseSwitch" destination="StG-nI-WpK" id="yYY-DO-U1D"/>
<outlet property="framerateSelector" destination="8hF-kb-2uV" id="dfT-hb-csE"/>
<outlet property="fullColorRangeStack" destination="iPd-Rg-St7" id="iPd-Out-Let"/>
<outlet property="fullColorRangeSwitch" destination="iPd-Wt-Ch9" id="iPd-Out-Swt"/>
@@ -2367,6 +2362,8 @@
<outlet property="hapticEngineStack" destination="MUp-Hc-VgH" id="XuW-ww-cmv"/>
<outlet property="hdrStack" destination="dH7-Sy-EdJ" id="cJm-bj-Aqo"/>
<outlet property="hdrSwitch" destination="wIq-fF-X8l" id="Ozz-h0-nJL"/>
<outlet property="interpolationLevelSlider" destination="FIp-Lv-Sel" id="FIp-Out-Sel"/>
<outlet property="interpolationLevelStack" destination="FIp-Lv-StK" id="FIp-Out-StK"/>
<outlet property="leftClickDelaySlider" destination="Mh4-1P-bSv" id="Tbj-Y0-t1x"/>
<outlet property="leftClickDelayStack" destination="q0S-CP-V6v" id="0lz-MN-OEb"/>
<outlet property="leftStickMinOffsetSlider" destination="KtJ-qH-KJX" id="vIV-4l-gbA"/>
@@ -2468,6 +2465,8 @@
<outlet property="squeezeShortcutSwitch" destination="VCg-Es-PQV" id="kgj-FT-fcZ"/>
<outlet property="statsOverlaySelector" destination="NLH-qN-qCo" id="EF8-JF-O3S"/>
<outlet property="statsOverlayStack" destination="UcP-0b-nJD" id="UdI-NS-O6k"/>
<outlet property="streamDimensionScaleSlider" destination="FIp-Sc-Sld" id="FIp-Out-ScS"/>
<outlet property="streamDimensionScaleStack" destination="FIp-Sc-StK" id="FIp-Out-ScK"/>
<outlet property="streamingRadialMenuDelaySlider" destination="qZX-tG-Pwf" id="V5Z-kn-irq"/>
<outlet property="streamingRadialMenuDelayStack" destination="xXr-dB-QWZ" id="I9M-3Y-HGg"/>
<outlet property="swapAbxyStack" destination="2rP-Ui-wVU" id="gUR-Vz-ORP"/>
@@ -3978,19 +3977,19 @@
<image name="tray.full.fill" catalog="system" width="128" height="88"/>
<image name="video.and.waveform" catalog="system" width="128" height="94"/>
<systemColor name="linkColor">
<color red="0.0" green="0.47843137254901963" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color red="0.0" green="0.47843137250000001" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>
<systemColor name="systemBackgroundColor">
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor>
<systemColor name="systemBrownColor">
<color red="0.67450980392156867" green="0.49803921568627452" blue="0.36862745098039218" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color red="0.63529411759999999" green="0.51764705879999995" blue="0.36862745099999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>
<systemColor name="systemGray3Color">
<color red="0.7803921568627451" green="0.7803921568627451" blue="0.80000000000000004" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color red="0.78039215689999997" green="0.78039215689999997" blue="0.80000000000000004" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>
<systemColor name="systemTealColor">
<color red="0.0" green="0.76470588235294112" blue="0.81568627450980391" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color red="0.18823529410000001" green="0.69019607839999997" blue="0.78039215689999997" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>
</resources>
</document>
+57 -56
View File
@@ -2405,12 +2405,13 @@
<color key="textColor" red="0.93902439019999995" green="0.9625305918" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<segmentedControl verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="q6v-Jb-ysy" userLabel="emulatedControllerTypeSelector">
<segmentedControl verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" apportionsSegmentWidthsByContent="YES" segmentControlStyle="plain" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="q6v-Jb-ysy" userLabel="emulatedControllerTypeSelector">
<rect key="frame" x="0.0" y="25.5" width="380" height="32"/>
<segments>
<segment title="Xbox 360"/>
<segment title="DS4"/>
<segment title="Both"/>
<segment title="PS"/>
<segment title="PS (Enhanced Haptics)"/>
<segment title="Xbox + PS"/>
</segments>
<color key="tintColor" red="0.6716768742" green="0.61711704730000005" blue="0.99902987480000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="selectedSegmentTintColor" systemColor="linkColor"/>
@@ -2551,37 +2552,37 @@
<stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" axis="vertical" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="FIi-Lv-StK" userLabel="interpolationLevel Stack">
<rect key="frame" x="456" y="2232" width="380" height="56.5"/>
<subviews>
<label verifyAmbiguity="off" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Interpolation Resolution" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="FIi-Lv-Lbl">
<label verifyAmbiguity="off" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Interpolation Resolution" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="FIi-Lv-Lbl">
<rect key="frame" x="0.0" y="0.0" width="380" height="20.5"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.93902439019999995" green="0.9625305918" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<slider verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" value="1" minValue="0.0" maxValue="1" translatesAutoresizingMaskIntoConstraints="NO" id="FIi-Lv-Sel" userLabel="Interpolation Level Slider">
<slider verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" value="1" minValue="0.0" maxValue="1" translatesAutoresizingMaskIntoConstraints="NO" id="FIi-Lv-Sel" userLabel="Interpolation Level Slider">
<rect key="frame" x="0.0" y="25.5" width="380" height="31"/>
</slider>
</subviews>
<constraints>
<constraint firstAttribute="width" constant="380" id="FIi-Lv-Wid"/>
<constraint firstAttribute="height" constant="56.5" id="FIi-Lv-Hgt"/>
<constraint firstAttribute="width" constant="380" id="FIi-Lv-Wid"/>
</constraints>
</stackView>
<stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" axis="vertical" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="FIi-Sc-StK" userLabel="streamDimensionScale Stack">
<rect key="frame" x="456" y="2293" width="380" height="56.5"/>
<subviews>
<label verifyAmbiguity="off" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Scaled Stream Resolution" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="FIi-Sc-Lbl">
<label verifyAmbiguity="off" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Scaled Stream Resolution" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="FIi-Sc-Lbl">
<rect key="frame" x="0.0" y="0.0" width="380" height="20.5"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.93902439019999995" green="0.9625305918" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<slider verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" value="1" minValue="0.0" maxValue="1" translatesAutoresizingMaskIntoConstraints="NO" id="FIi-Sc-Sld" userLabel="Stream Dimension Scale Slider">
<slider verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" value="1" minValue="0.0" maxValue="1" translatesAutoresizingMaskIntoConstraints="NO" id="FIi-Sc-Sld" userLabel="Stream Dimension Scale Slider">
<rect key="frame" x="0.0" y="25.5" width="380" height="31"/>
</slider>
</subviews>
<constraints>
<constraint firstAttribute="width" constant="380" id="FIi-Sc-Wid"/>
<constraint firstAttribute="height" constant="56.5" id="FIi-Sc-Hgt"/>
<constraint firstAttribute="width" constant="380" id="FIi-Sc-Wid"/>
</constraints>
</stackView>
<stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="CKa-yk-GSi" userLabel="redirectMicStack">
@@ -3127,28 +3128,8 @@
<constraint firstAttribute="height" constant="56.5" id="MNC-Cn-lnb"/>
</constraints>
</stackView>
<stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="HCc-SK-NBk">
<rect key="frame" x="459" y="2252" width="280" height="31"/>
<subviews>
<label verifyAmbiguity="off" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Enable Timebase" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.64999997615814209" adjustsLetterSpacingToFitWidth="YES" adjustsFontForContentSizeCategory="YES" translatesAutoresizingMaskIntoConstraints="NO" id="P4N-i6-1zD" userLabel="uiLabel">
<rect key="frame" x="0.0" y="0.0" width="130" height="31"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="bwf-wJ-EC2">
<rect key="frame" x="150" y="0.0" width="132" height="31"/>
<constraints>
<constraint firstAttribute="width" constant="130" id="KUb-Mq-m9s"/>
</constraints>
</switch>
</subviews>
<constraints>
<constraint firstAttribute="height" constant="31" id="qzI-PN-1ml"/>
</constraints>
</stackView>
<stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="dZ3-os-MKX">
<rect key="frame" x="459" y="2291" width="312" height="31"/>
<rect key="frame" x="455" y="2380" width="312" height="31"/>
<subviews>
<label verifyAmbiguity="off" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Async Frame Deueue" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.64999997615814209" adjustsLetterSpacingToFitWidth="YES" adjustsFontForContentSizeCategory="YES" translatesAutoresizingMaskIntoConstraints="NO" id="e1n-pw-04X" userLabel="uiLabel">
<rect key="frame" x="0.0" y="0.0" width="162" height="31"/>
@@ -3167,26 +3148,6 @@
<constraint firstAttribute="height" constant="31" id="O8n-y9-TrQ"/>
</constraints>
</stackView>
<stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="Rw1-5R-Jjf" userLabel="SDR Performance Workaround Stack">
<rect key="frame" x="459" y="2330" width="383" height="31"/>
<subviews>
<label verifyAmbiguity="off" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="SDR Performance Workaround" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.64999997615814209" adjustsLetterSpacingToFitWidth="YES" adjustsFontForContentSizeCategory="YES" translatesAutoresizingMaskIntoConstraints="NO" id="F8d-OQ-PsH" userLabel="uiLabel">
<rect key="frame" x="0.0" y="0.0" width="233" height="31"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Y5l-G9-PyG">
<rect key="frame" x="253" y="0.0" width="132" height="31"/>
<constraints>
<constraint firstAttribute="width" constant="130" id="Qwc-Ko-5ZE"/>
</constraints>
</switch>
</subviews>
<constraints>
<constraint firstAttribute="height" constant="31" id="abg-h5-3Ml"/>
</constraints>
</stackView>
<stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="fqe-49-wG1">
<rect key="frame" x="15" y="1643" width="309.5" height="31"/>
<subviews>
@@ -3551,6 +3512,46 @@
<constraint firstAttribute="width" constant="380" id="tSR-LD-qKK"/>
</constraints>
</stackView>
<stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="Rw1-5R-Jjf" userLabel="SDR Performance Workaround Stack">
<rect key="frame" x="734" y="2380" width="383" height="31"/>
<subviews>
<label verifyAmbiguity="off" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="SDR Performance Workaround" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.64999997615814209" adjustsLetterSpacingToFitWidth="YES" adjustsFontForContentSizeCategory="YES" translatesAutoresizingMaskIntoConstraints="NO" id="F8d-OQ-PsH" userLabel="uiLabel">
<rect key="frame" x="0.0" y="0.0" width="233" height="31"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Y5l-G9-PyG">
<rect key="frame" x="253" y="0.0" width="132" height="31"/>
<constraints>
<constraint firstAttribute="width" constant="130" id="Qwc-Ko-5ZE"/>
</constraints>
</switch>
</subviews>
<constraints>
<constraint firstAttribute="height" constant="31" id="abg-h5-3Ml"/>
</constraints>
</stackView>
<stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" axis="vertical" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="7Ye-hW-K31" userLabel="dualSenseTransientStack">
<rect key="frame" x="437" y="1540" width="380" height="55.5"/>
<subviews>
<label verifyAmbiguity="off" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="DualSense Haptic Intensity" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="vak-fg-iAQ">
<rect key="frame" x="0.0" y="0.0" width="380" height="20.5"/>
<color key="tintColor" red="0.81533776120000001" green="0.85979419950000002" blue="0.91372549020000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<slider verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" minValue="0.0" maxValue="2" translatesAutoresizingMaskIntoConstraints="NO" id="8fE-0o-N0V" userLabel="dualSenseTransientSlider">
<rect key="frame" x="0.0" y="25.5" width="380" height="30"/>
<color key="tintColor" systemColor="linkColor"/>
</slider>
</subviews>
<constraints>
<constraint firstAttribute="height" constant="55.5" id="8g8-0d-mPf"/>
<constraint firstAttribute="width" constant="380" id="WTF-gm-sAY"/>
</constraints>
</stackView>
</subviews>
<viewLayoutGuide key="safeArea" id="0sf-mG-PCk"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
@@ -3591,6 +3592,8 @@
<outlet property="customResolutionSwitch" destination="kRO-HO-zKO" id="ZUP-Yd-QDY"/>
<outlet property="delayLeftClickStack" destination="fjb-oT-r1E" id="0Tb-f9-Pym"/>
<outlet property="delayLeftClickSwitch" destination="YB5-xe-Zdx" id="FEK-Lb-f0r"/>
<outlet property="dualSenseTransientSlider" destination="8fE-0o-N0V" id="ECI-p9-Fn4"/>
<outlet property="dualSenseTransientStack" destination="7Ye-hW-K31" id="IaM-oQ-DVA"/>
<outlet property="duckOtherAppStack" destination="FkE-Sc-6kA" id="YUO-j1-Bdv"/>
<outlet property="duckOtherAppSwitch" destination="71J-Gt-A3D" id="3iT-jh-hWx"/>
<outlet property="edgeSlidingSensitivitySlider" destination="KRf-2B-Reb" id="UaG-GM-wkO"/>
@@ -3605,15 +3608,9 @@
<outlet property="fpsStack" destination="aMv-7Q-yYg" id="JxU-wq-2Za"/>
<outlet property="framePacingModeSelector" destination="CC6-pu-Uoh" id="HTB-6I-QgB"/>
<outlet property="framePacingStack" destination="U0H-kh-QLS" id="M8w-wy-HI8"/>
<outlet property="interpolationLevelSlider" destination="FIi-Lv-Sel" id="FIi-Out-Sel"/>
<outlet property="interpolationLevelStack" destination="FIi-Lv-StK" id="FIi-Out-StK"/>
<outlet property="streamDimensionScaleSlider" destination="FIi-Sc-Sld" id="FIi-Out-ScS"/>
<outlet property="streamDimensionScaleStack" destination="FIi-Sc-StK" id="FIi-Out-ScK"/>
<outlet property="frameQueueSizeLabel" destination="r8X-jt-JfH" id="Tr9-eb-bIx"/>
<outlet property="frameQueueSizeSlider" destination="thv-JE-onX" id="gKD-6c-5tw"/>
<outlet property="frameQueueSizeStack" destination="IHx-FO-ctr" id="gob-Ea-Avh"/>
<outlet property="frameTimebaseStack" destination="HCc-SK-NBk" id="xts-bc-f0C"/>
<outlet property="frameTimebaseSwitch" destination="bwf-wJ-EC2" id="fwl-GA-bQ6"/>
<outlet property="framerateSelector" destination="dAN-Gy-Eg7" id="tMv-Va-PhO"/>
<outlet property="fullColorRangeStack" destination="fRg-Rn-St7" id="fRg-Out-Let"/>
<outlet property="fullColorRangeSwitch" destination="fRs-Wt-Ch9" id="fRs-Out-Let"/>
@@ -3634,6 +3631,8 @@
<outlet property="hapticEngineStack" destination="kiR-q0-FQX" id="zj5-eD-N5a"/>
<outlet property="hdrStack" destination="kFw-d7-BTn" id="3aT-Gh-bDO"/>
<outlet property="hdrSwitch" destination="Kgb-YU-AFW" id="ksG-A4-HhS"/>
<outlet property="interpolationLevelSlider" destination="FIi-Lv-Sel" id="FIi-Out-Sel"/>
<outlet property="interpolationLevelStack" destination="FIi-Lv-StK" id="FIi-Out-StK"/>
<outlet property="leftClickDelaySlider" destination="HNu-vj-Q0t" id="X9f-ya-wVv"/>
<outlet property="leftClickDelayStack" destination="muF-2y-dHu" id="rmd-Pm-nfV"/>
<outlet property="leftStickMinOffsetSlider" destination="vOQ-zv-JOQ" id="kJb-ey-xMh"/>
@@ -3720,6 +3719,8 @@
<outlet property="softKeyboardToolbarSwitch" destination="c4W-2p-R3A" id="gf6-pq-L8o"/>
<outlet property="statsOverlaySelector" destination="Bda-3L-Wtg" id="9kj-wY-VjF"/>
<outlet property="statsOverlayStack" destination="dby-k3-aiH" id="1Ha-Y3-su4"/>
<outlet property="streamDimensionScaleSlider" destination="FIi-Sc-Sld" id="FIi-Out-ScS"/>
<outlet property="streamDimensionScaleStack" destination="FIi-Sc-StK" id="FIi-Out-ScK"/>
<outlet property="streamingRadialMenuDelaySlider" destination="Tgh-nL-w5K" id="91f-Q5-IIl"/>
<outlet property="streamingRadialMenuDelayStack" destination="ySL-A5-fcn" id="HrE-C7-y0g"/>
<outlet property="swapAbxyStack" destination="reE-vr-Mdk" id="y3v-ac-LHy"/>
+9
View File
@@ -63,6 +63,15 @@
rightSharpness:(float)rightSharpness
rightTransient:(float)rightTransient
delaySeconds:(double)delaySeconds;
-(BOOL) hasDualSenseController:(uint16_t)controllerNumber;
-(void) renderDeviceDualSenseHaptics:(uint16_t)controllerNumber
leftAmplitude:(float)leftAmplitude
leftSharpness:(float)leftSharpness
leftTransient:(float)leftTransient
rightAmplitude:(float)rightAmplitude
rightSharpness:(float)rightSharpness
rightTransient:(float)rightTransient
delaySeconds:(double)delaySeconds;
-(void) cancelScheduledDualSenseHaptics;
-(void) updateTimerStateForOsc;
+381 -235
View File
@@ -184,9 +184,11 @@ static void ApplyAdaptiveTriggerEffect(GCDualSenseAdaptiveTrigger* trigger,
case RumbleDevice:
[_oscController.lowFreqMotor setMotorAmplitude:lowFreqMotor];
[_oscController.highFreqMotor setMotorAmplitude:highFreqMotor];
break;
case LeftRightSwapped:
[voidController.lowFreqMotor setMotorAmplitude:highFreqMotor];
[voidController.highFreqMotor setMotorAmplitude:lowFreqMotor];
break;
case RumbleOff:
break;
default:
@@ -212,250 +214,317 @@ static void ApplyAdaptiveTriggerEffect(GCDualSenseAdaptiveTrigger* trigger,
return CGRectGetWidth([[UIScreen mainScreen]bounds]) > CGRectGetHeight([[UIScreen mainScreen]bounds]);
}
-(void)updateTimerStateForController:(VoidController* )voidController{
// if (@available(iOS 14.0, tvOS 14.0, *)) {
if (true) {
if(_gyroMode == GyroModeOff){
[self stopTimerForController:voidController];
return;
}
if(voidController.gamepad.motion.hasAttitudeAndRotationRate) [voidController.motionTypes addObject:@(LI_MOTION_TYPE_ACCEL)];
if (@available(iOS 14.0, *)) if(voidController.gamepad.motion.hasRotationRate) [voidController.motionTypes addObject:@(LI_MOTION_TYPE_GYRO)];
#if !TARGET_OS_TV
- (UIInterfaceOrientation)currentInterfaceOrientation
{
if (@available(iOS 13.0, *)) {
for (UIScene *scene in UIApplication.sharedApplication.connectedScenes) {
if (![scene isKindOfClass:UIWindowScene.class]) {
continue;
}
for(NSNumber* motionTypeObj in voidController.motionTypes){
uint8_t motionType = motionTypeObj.intValue;
UIWindowScene *windowScene = (UIWindowScene *)scene;
if (windowScene.activationState != UISceneActivationStateForegroundActive) {
continue;
}
if (windowScene.interfaceOrientation != UIInterfaceOrientationUnknown) {
return windowScene.interfaceOrientation;
}
}
for (UIScene *scene in UIApplication.sharedApplication.connectedScenes) {
if (![scene isKindOfClass:UIWindowScene.class]) {
continue;
}
UIWindowScene *windowScene = (UIWindowScene *)scene;
if (windowScene.activationState == UISceneActivationStateUnattached) {
continue;
}
if (windowScene.interfaceOrientation != UIInterfaceOrientationUnknown) {
return windowScene.interfaceOrientation;
}
}
}
return UIApplication.sharedApplication.statusBarOrientation;
}
#endif
-(void)updateTimerStateForController:(VoidController* )voidController{
if (![NSThread isMainThread]) {
dispatch_async(dispatch_get_main_queue(), ^{
[self updateTimerStateForController:voidController];
});
return;
}
if (voidController == nil || voidController.reportRateHz == 0) {
[self stopTimerForController:voidController];
return;
}
// if (@available(iOS 14.0, tvOS 14.0, *)) {
if(_gyroMode == GyroModeOff){
[self stopTimerForController:voidController];
return;
}
if([ControllerUtil hasControllerAccelerometer:voidController.gamepad]) {
[voidController.motionTypes addObject:@(LI_MOTION_TYPE_ACCEL)];
}
if (@available(iOS 14.0, *)) if(voidController.gamepad.motion.hasRotationRate){
[voidController.motionTypes addObject:@(LI_MOTION_TYPE_GYRO)];
}
for(NSNumber* motionTypeObj in voidController.motionTypes){
uint8_t motionType = motionTypeObj.intValue;
#if !TARGET_OS_TV //tvOS has no device motion
if(voidController == _oscController){
//Player has no controller *or* no motion for controller 1 *or* wants to override controller 1 motion with device motion
if(!voidController.motionManager) {
voidController.motionManager = [[CMMotionManager alloc] init];
}
switch (motionType) {
case LI_MOTION_TYPE_ACCEL:
[voidController.accelTimer invalidate];
voidController.accelTimer = nil;
// Reset the last motion sample
CMAcceleration emptyDeviceAccelSample = {};
voidController.lastDeviceAccelSample = emptyDeviceAccelSample;
{dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"setup device built-in gyro accelTimer");
if(voidController == _oscController){
//Player has no controller *or* no motion for controller 1 *or* wants to override controller 1 motion with device motion
if(!voidController.motionManager) {
voidController.motionManager = [[CMMotionManager alloc] init];
}
switch (motionType) {
case LI_MOTION_TYPE_ACCEL: {
[voidController.accelTimer invalidate];
voidController.accelTimer = nil;
// Reset the last motion sample
CMAcceleration emptyDeviceAccelSample = {};
voidController.lastDeviceAccelSample = emptyDeviceAccelSample;
voidController.motionManager.deviceMotionUpdateInterval = 1.0 / voidController.reportRateHz;
[voidController.motionManager startDeviceMotionUpdates];
{NSLog(@"setup device built-in accelTimer");
voidController.hasAccelerometer = YES;
voidController.accelTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 / voidController.reportRateHz repeats:YES block:^(NSTimer *timer) {
// Don't send duplicate samples
CMAcceleration lastDeviceAccelSample = voidController.lastDeviceAccelSample;
CMAcceleration deviceAccelSample = voidController.motionManager.deviceMotion.userAcceleration;
//userAcceleration does not contain gravity, add gravity to x, y and z values:
deviceAccelSample.x += voidController.motionManager.deviceMotion.gravity.x * self->_gyroSensitivity;
deviceAccelSample.y += voidController.motionManager.deviceMotion.gravity.y * self->_gyroSensitivity;
deviceAccelSample.z += voidController.motionManager.deviceMotion.gravity.z * self->_gyroSensitivity;
if (memcmp(&deviceAccelSample, &lastDeviceAccelSample, sizeof(deviceAccelSample)) == 0) {
return;
}
voidController.lastDeviceAccelSample = deviceAccelSample;
// Convert g to m/s^2
if (@available(iOS 13.0, *)) {
if(UIApplication.sharedApplication.windows.firstObject.windowScene.interfaceOrientation == 4){ //check for landscape left or landscape right
LiSendControllerMotionEvent((uint8_t)voidController.controllerNumber,
LI_MOTION_TYPE_ACCEL,
deviceAccelSample.y * -9.80665f * self->_gyroSensitivity,
deviceAccelSample.z * -9.80665f * self->_gyroSensitivity,
deviceAccelSample.x * -9.80665f * self->_gyroSensitivity);
}
else{
LiSendControllerMotionEvent((uint8_t)voidController.controllerNumber,
LI_MOTION_TYPE_ACCEL,
deviceAccelSample.y * +9.80665f * self->_gyroSensitivity,
deviceAccelSample.z * -9.80665f * self->_gyroSensitivity,
deviceAccelSample.x * +9.80665f * self->_gyroSensitivity);
}
}
else{
LiSendControllerMotionEvent((uint8_t)voidController.controllerNumber,
LI_MOTION_TYPE_ACCEL,
deviceAccelSample.y * -9.80665f * self->_gyroSensitivity,
deviceAccelSample.z * -9.80665f * self->_gyroSensitivity,
deviceAccelSample.x * -9.80665f * self->_gyroSensitivity);
}
}];
});}
break;
case LI_MOTION_TYPE_GYRO:
[voidController.gyroTimer invalidate];
voidController.gyroTimer = nil;
// Reset the last motion sample
CMRotationRate emptyDeviceGyroSample = {};
voidController.lastDeviceGyroSample = emptyDeviceGyroSample;
[voidController.motionManager startDeviceMotionUpdates];
NSLog(@"setup device built-in gyro gyroTimer");
voidController.hasGyroscope = YES;
voidController.gyroTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 / voidController.reportRateHz repeats:YES block:^(NSTimer *timer) {
// Don't send duplicate samples
CMRotationRate lastDeviceGyroSample = voidController.lastDeviceGyroSample;
CMRotationRate deviceGyroSample = voidController.motionManager.deviceMotion.rotationRate;
if (memcmp(&deviceGyroSample, &lastDeviceGyroSample, sizeof(deviceGyroSample)) == 0) {
return;
}
voidController.lastDeviceGyroSample = deviceGyroSample;
// Convert rad/s to deg/s
UIInterfaceOrientation interfaceOrientation = UIInterfaceOrientationUnknown;
if (@available(iOS 13.0, *)) {
interfaceOrientation = UIApplication.sharedApplication.windows.firstObject.windowScene.interfaceOrientation;
} else {
interfaceOrientation = UIApplication.sharedApplication.statusBarOrientation;
}
deviceAccelSample.x += voidController.motionManager.deviceMotion.gravity.x;
deviceAccelSample.y += voidController.motionManager.deviceMotion.gravity.y;
deviceAccelSample.z += voidController.motionManager.deviceMotion.gravity.z;
NSLog(@"sending device accel %f", CACurrentMediaTime());
UIInterfaceOrientation interfaceOrientation = [self currentInterfaceOrientation];
CMAcceleration mappedDeviceAccelSample = {};
switch (interfaceOrientation) {
case UIInterfaceOrientationLandscapeLeft:
LiSendControllerMotionEvent((uint8_t)voidController.controllerNumber,
LI_MOTION_TYPE_GYRO,
self->_gyroEnabledFlag ? deviceGyroSample.y * 57.2957795f * self->_gyroSensitivity : 0,
self->_gyroEnabledFlag ? deviceGyroSample.z * 57.2957795f * self->_gyroSensitivity : 0,
self->_gyroEnabledFlag ? deviceGyroSample.x * 57.2957795f * self->_gyroSensitivity : 0);
mappedDeviceAccelSample.x = deviceAccelSample.y * -9.80665f;
mappedDeviceAccelSample.y = deviceAccelSample.z * -9.80665f;
mappedDeviceAccelSample.z = deviceAccelSample.x * -9.80665f;
break;
case UIInterfaceOrientationLandscapeRight:
LiSendControllerMotionEvent((uint8_t)voidController.controllerNumber,
LI_MOTION_TYPE_GYRO,
self->_gyroEnabledFlag ? deviceGyroSample.y * -57.2957795f * self->_gyroSensitivity : 0,
self->_gyroEnabledFlag ? deviceGyroSample.z * 57.2957795f * self->_gyroSensitivity : 0,
self->_gyroEnabledFlag ? deviceGyroSample.x * -57.2957795f * self->_gyroSensitivity : 0);
mappedDeviceAccelSample.x = deviceAccelSample.y * 9.80665f;
mappedDeviceAccelSample.y = deviceAccelSample.z * -9.80665f;
mappedDeviceAccelSample.z = deviceAccelSample.x * 9.80665f;
break;
case UIInterfaceOrientationPortrait:
LiSendControllerMotionEvent((uint8_t)voidController.controllerNumber,
LI_MOTION_TYPE_GYRO,
self->_gyroEnabledFlag ? deviceGyroSample.x * 57.2957795f * self->_gyroSensitivity : 0,
self->_gyroEnabledFlag ? deviceGyroSample.z * 57.2957795f * self->_gyroSensitivity : 0,
self->_gyroEnabledFlag ? deviceGyroSample.y * -57.2957795f * self->_gyroSensitivity : 0);
mappedDeviceAccelSample.x = deviceAccelSample.x * -9.80665f;
mappedDeviceAccelSample.y = deviceAccelSample.z * -9.80665f;
mappedDeviceAccelSample.z = deviceAccelSample.y * 9.80665f;
break;
case UIInterfaceOrientationPortraitUpsideDown:
LiSendControllerMotionEvent((uint8_t)voidController.controllerNumber,
LI_MOTION_TYPE_GYRO,
self->_gyroEnabledFlag ? deviceGyroSample.x * -57.2957795f * self->_gyroSensitivity : 0,
self->_gyroEnabledFlag ? deviceGyroSample.z * 57.2957795f * self->_gyroSensitivity : 0,
self->_gyroEnabledFlag ? deviceGyroSample.y * 57.2957795f * self->_gyroSensitivity : 0);
mappedDeviceAccelSample.x = deviceAccelSample.x * 9.80665f;
mappedDeviceAccelSample.y = deviceAccelSample.z * -9.80665f;
mappedDeviceAccelSample.z = deviceAccelSample.y * -9.80665f;
break;
default:
LiSendControllerMotionEvent((uint8_t)voidController.controllerNumber,
LI_MOTION_TYPE_GYRO,
self->_gyroEnabledFlag ? deviceGyroSample.y * 57.2957795f * self->_gyroSensitivity : 0,
self->_gyroEnabledFlag ? deviceGyroSample.z * 57.2957795f * self->_gyroSensitivity : 0,
self->_gyroEnabledFlag ? deviceGyroSample.x * 57.2957795f * self->_gyroSensitivity : 0);
mappedDeviceAccelSample.x = deviceAccelSample.y * -9.80665f;
mappedDeviceAccelSample.y = deviceAccelSample.z * -9.80665f;
mappedDeviceAccelSample.z = deviceAccelSample.x * -9.80665f;
break;
}
// Don't send duplicate samples after orientation remapping.
CMAcceleration lastDeviceAccelSample = voidController.lastDeviceAccelSample;
if (memcmp(&mappedDeviceAccelSample, &lastDeviceAccelSample, sizeof(mappedDeviceAccelSample)) == 0) {
return;
}
voidController.lastDeviceAccelSample = mappedDeviceAccelSample;
/*
if(UIApplication.sharedApplication.windows.firstObject.windowScene.interfaceOrientation == 4){//check for landscape left or landscape right
// Convert g to m/s^2
LiSendControllerMotionEvent((uint8_t)voidController.controllerNumber,
LI_MOTION_TYPE_ACCEL,
mappedDeviceAccelSample.x,
mappedDeviceAccelSample.y,
mappedDeviceAccelSample.z);
}];}
break;
}
case LI_MOTION_TYPE_GYRO: {
[voidController.gyroTimer invalidate];
voidController.gyroTimer = nil;
// Reset the last motion sample
CMRotationRate emptyDeviceGyroSample = {};
voidController.lastDeviceGyroSample = emptyDeviceGyroSample;
[voidController.motionManager startDeviceMotionUpdates];
NSLog(@"setup device built-in gyro gyroTimer");
voidController.hasGyroscope = YES;
voidController.gyroTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 / voidController.reportRateHz repeats:YES block:^(NSTimer *timer) {
// Don't send duplicate samples
CMRotationRate lastDeviceGyroSample = voidController.lastDeviceGyroSample;
CMRotationRate deviceGyroSample = voidController.motionManager.deviceMotion.rotationRate;
if (memcmp(&deviceGyroSample, &lastDeviceGyroSample, sizeof(deviceGyroSample)) == 0) {
return;
}
voidController.lastDeviceGyroSample = deviceGyroSample;
// Convert rad/s to deg/s
UIInterfaceOrientation interfaceOrientation = [self currentInterfaceOrientation];
switch (interfaceOrientation) {
case UIInterfaceOrientationLandscapeLeft:
LiSendControllerMotionEvent((uint8_t)voidController.controllerNumber,
LI_MOTION_TYPE_GYRO,
self->_gyroEnabledFlag ? deviceGyroSample.y * 57.2957795f * self->_gyroSensitivity : 0,
self->_gyroEnabledFlag ? deviceGyroSample.z * 57.2957795f * self->_gyroSensitivity : 0,
self->_gyroEnabledFlag ? deviceGyroSample.x * 57.2957795f * self->_gyroSensitivity : 0);
}
else{
break;
case UIInterfaceOrientationLandscapeRight:
LiSendControllerMotionEvent((uint8_t)voidController.controllerNumber,
LI_MOTION_TYPE_GYRO,
self->_gyroEnabledFlag ? deviceGyroSample.y * -57.2957795f * self->_gyroSensitivity : 0,
self->_gyroEnabledFlag ? deviceGyroSample.z * 57.2957795f * self->_gyroSensitivity : 0,
self->_gyroEnabledFlag ? deviceGyroSample.x * -57.2957795f * self->_gyroSensitivity : 0);
}
*/
}];
break;
}
}
#endif
else{
NSLog(@"controller obj timer update: controller timer ");
if (@available(iOS 14.0, *)) {
switch (motionType) {
case LI_MOTION_TYPE_ACCEL:
[voidController.accelTimer invalidate];
voidController.accelTimer = nil;
if (voidController.reportRateHz && voidController.gamepad.motion.hasGravityAndUserAcceleration) {
// Reset the last motion sample
GCAcceleration emptyAccelSample = {};
voidController.lastAccelSample = emptyAccelSample;
NSLog(@"setup controller gyro accelTimer");
dispatch_sync(dispatch_get_main_queue(), ^{
voidController.hasAccelerometer = YES;
voidController.accelTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 / voidController.reportRateHz repeats:YES block:^(NSTimer *timer) {
// Don't send duplicate samples
GCAcceleration lastAccelSample = voidController.lastAccelSample;
GCAcceleration accelSample = voidController.gamepad.motion.acceleration;
if (memcmp(&accelSample, &lastAccelSample, sizeof(accelSample)) == 0) {
return;
}
voidController.lastAccelSample = accelSample;
// Convert g to m/s^2
//NSLog(@"sending controller gyro data, accelSample data 00: %f, playerIndex: %ld, obj: %@",accelSample.x, (long)voidController.gamepad.playerIndex, voidController);
LiSendControllerMotionEvent((uint8_t)voidController.controllerNumber,
LI_MOTION_TYPE_ACCEL,
accelSample.x * -9.80665f * self->_gyroSensitivity,
accelSample.y * -9.80665f * self->_gyroSensitivity,
accelSample.z * -9.80665f * self->_gyroSensitivity);
}];
});
}
break;
case LI_MOTION_TYPE_GYRO:
[voidController.gyroTimer invalidate];
voidController.gyroTimer = nil;
if (voidController.reportRateHz && voidController.gamepad.motion.hasRotationRate) {
// Reset the last motion sample
GCRotationRate emptyGyroSample = {};
voidController.lastGyroSample = emptyGyroSample;
//dispatch_sync(dispatch_get_main_queue(), ^{
{dispatch_async(dispatch_get_main_queue(), ^{
voidController.hasGyroscope = YES;
voidController.gyroTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 / voidController.reportRateHz repeats:YES block:^(NSTimer *timer) {
// Don't send duplicate samples
GCRotationRate lastGyroSample = voidController.lastGyroSample;
GCRotationRate gyroSample = voidController.gamepad.motion.rotationRate;
if (memcmp(&gyroSample, &lastGyroSample, sizeof(gyroSample)) == 0) {
return;
}
voidController.lastGyroSample = gyroSample;
// Convert rad/s to deg/s
// NSLog(@"sending controller gyro data, gyroSample data 00: %f, playerIndex: %ld, obj: %@",gyroSample.x, (long)voidController.gamepad.playerIndex, voidController);
LiSendControllerMotionEvent((uint8_t)voidController.controllerNumber,
LI_MOTION_TYPE_GYRO,
self->_gyroEnabledFlag ? gyroSample.x * 57.2957795f * self->_gyroSensitivity : 0,
self->_gyroEnabledFlag ? gyroSample.z * 57.2957795f * self->_gyroSensitivity : 0,
self->_gyroEnabledFlag ? gyroSample.y * -57.2957795f * self->_gyroSensitivity : 0);
}];
// });
});}
}
break;
}
break;
case UIInterfaceOrientationPortrait:
LiSendControllerMotionEvent((uint8_t)voidController.controllerNumber,
LI_MOTION_TYPE_GYRO,
self->_gyroEnabledFlag ? deviceGyroSample.x * 57.2957795f * self->_gyroSensitivity : 0,
self->_gyroEnabledFlag ? deviceGyroSample.z * 57.2957795f * self->_gyroSensitivity : 0,
self->_gyroEnabledFlag ? deviceGyroSample.y * -57.2957795f * self->_gyroSensitivity : 0);
break;
case UIInterfaceOrientationPortraitUpsideDown:
LiSendControllerMotionEvent((uint8_t)voidController.controllerNumber,
LI_MOTION_TYPE_GYRO,
self->_gyroEnabledFlag ? deviceGyroSample.x * -57.2957795f * self->_gyroSensitivity : 0,
self->_gyroEnabledFlag ? deviceGyroSample.z * 57.2957795f * self->_gyroSensitivity : 0,
self->_gyroEnabledFlag ? deviceGyroSample.y * 57.2957795f * self->_gyroSensitivity : 0);
break;
default:
LiSendControllerMotionEvent((uint8_t)voidController.controllerNumber,
LI_MOTION_TYPE_GYRO,
self->_gyroEnabledFlag ? deviceGyroSample.y * 57.2957795f * self->_gyroSensitivity : 0,
self->_gyroEnabledFlag ? deviceGyroSample.z * 57.2957795f * self->_gyroSensitivity : 0,
self->_gyroEnabledFlag ? deviceGyroSample.x * 57.2957795f * self->_gyroSensitivity : 0);
break;
}
/*
if(UIApplication.sharedApplication.windows.firstObject.windowScene.interfaceOrientation == 4){//check for landscape left or landscape right
LiSendControllerMotionEvent((uint8_t)voidController.controllerNumber,
LI_MOTION_TYPE_GYRO,
self->_gyroEnabledFlag ? deviceGyroSample.y * 57.2957795f * self->_gyroSensitivity : 0,
self->_gyroEnabledFlag ? deviceGyroSample.z * 57.2957795f * self->_gyroSensitivity : 0,
self->_gyroEnabledFlag ? deviceGyroSample.x * 57.2957795f * self->_gyroSensitivity : 0);
}
else{
LiSendControllerMotionEvent((uint8_t)voidController.controllerNumber,
LI_MOTION_TYPE_GYRO,
self->_gyroEnabledFlag ? deviceGyroSample.y * -57.2957795f * self->_gyroSensitivity : 0,
self->_gyroEnabledFlag ? deviceGyroSample.z * 57.2957795f * self->_gyroSensitivity : 0,
self->_gyroEnabledFlag ? deviceGyroSample.x * -57.2957795f * self->_gyroSensitivity : 0);
}
*/
}];
break;
}
}
}
NSLog(@"controller obj timer, motionTypes: %lu", (unsigned long)voidController.motionTypes.count);
// Set the motion sensor state if they require manual activation
[self updateSensorSateForController:voidController];
// NSLog(@"sensor active: %d", voidController.gamepad.motion.sensorsActive);
#endif
else{
// NSLog(@"controller obj timer update: controller timer ");
if (@available(iOS 14.0, *)) {
switch (motionType) {
case LI_MOTION_TYPE_ACCEL:
[voidController.accelTimer invalidate];
voidController.accelTimer = nil;
// if (voidController.reportRateHz && voidController.gamepad.motion.hasGravityAndUserAcceleration) {
if (voidController.reportRateHz) {
// Reset the last motion sample
GCAcceleration emptyAccelSample = {};
voidController.lastAccelSample = emptyAccelSample;
void (^setupAccelTimer)(void) = ^{
voidController.hasAccelerometer = YES;
voidController.accelTimer =
[NSTimer scheduledTimerWithTimeInterval:1.0 / voidController.reportRateHz
repeats:YES
block:^(NSTimer *timer) {
GCAcceleration lastAccelSample = voidController.lastAccelSample;
GCAcceleration accelSample = voidController.gamepad.motion.acceleration;
if (memcmp(&accelSample, &lastAccelSample, sizeof(accelSample)) == 0) {
return;
}
voidController.lastAccelSample = accelSample;
LiSendControllerMotionEvent((uint8_t)voidController.controllerNumber,
LI_MOTION_TYPE_ACCEL,
accelSample.x * -9.80665f,
accelSample.y * -9.80665f,
accelSample.z * -9.80665f);
}];
};
if ([NSThread isMainThread]) {
setupAccelTimer();
} else {
dispatch_async(dispatch_get_main_queue(), setupAccelTimer);
}
}
break;
case LI_MOTION_TYPE_GYRO:
[voidController.gyroTimer invalidate];
voidController.gyroTimer = nil;
if (voidController.reportRateHz && voidController.gamepad.motion.hasRotationRate) {
// Reset the last motion sample
GCRotationRate emptyGyroSample = {};
voidController.lastGyroSample = emptyGyroSample;
//dispatch_sync(dispatch_get_main_queue(), ^{
void (^setupGyroTimer)(void) = ^{
voidController.hasGyroscope = YES;
voidController.gyroTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 / voidController.reportRateHz repeats:YES block:^(NSTimer *timer) {
// Don't send duplicate samples
GCRotationRate lastGyroSample = voidController.lastGyroSample;
GCRotationRate gyroSample = voidController.gamepad.motion.rotationRate;
if (memcmp(&gyroSample, &lastGyroSample, sizeof(gyroSample)) == 0) {
return;
}
voidController.lastGyroSample = gyroSample;
// Convert rad/s to deg/s
// NSLog(@"sending controller gyro data, gyroSample data 00: %f, playerIndex: %ld, obj: %@",gyroSample.x, (long)voidController.gamepad.playerIndex, voidController);
LiSendControllerMotionEvent((uint8_t)voidController.controllerNumber,
LI_MOTION_TYPE_GYRO,
self->_gyroEnabledFlag ? gyroSample.x * 57.2957795f * self->_gyroSensitivity : 0,
self->_gyroEnabledFlag ? gyroSample.z * 57.2957795f * self->_gyroSensitivity : 0,
self->_gyroEnabledFlag ? gyroSample.y * -57.2957795f * self->_gyroSensitivity : 0);
}];
};
if ([NSThread isMainThread]) setupGyroTimer();
else dispatch_async(dispatch_get_main_queue(), setupGyroTimer);
}
break;
}
}
}
}
NSLog(@"controller obj timer, motionTypes: %lu", (unsigned long)voidController.motionTypes.count);
// Set the motion sensor state if they require manual activation
[self updateSensorSateForController:voidController];
// NSLog(@"sensor active: %d", voidController.gamepad.motion.sensorsActive);
}
- (void)updateSensorSateForController:(VoidController* )voidController{
@@ -472,6 +541,13 @@ static void ApplyAdaptiveTriggerEffect(GCDualSenseAdaptiveTrigger* trigger,
}
- (void) setMotionEventState:(uint16_t)controllerNumber motionType:(uint8_t)motionType reportRateHz:(uint16_t)reportRateHz {
if (![NSThread isMainThread]) {
dispatch_async(dispatch_get_main_queue(), ^{
[self setMotionEventState:controllerNumber motionType:motionType reportRateHz:reportRateHz];
});
return;
}
//if (@available(iOS 14.0, tvOS 14.0, *)) {
NSLog(@"gyroMode: %ld", (long)_gyroMode);
@@ -483,16 +559,26 @@ static void ApplyAdaptiveTriggerEffect(GCDualSenseAdaptiveTrigger* trigger,
if(!voidController.motionTypes){
voidController.motionTypes = [[NSMutableSet alloc] init];
}
[voidController.motionTypes addObject:@(motionType)];
if (reportRateHz == 0) {
[voidController.motionTypes removeObject:@(motionType)];
}
else {
[voidController.motionTypes addObject:@(motionType)];
}
voidController.hasGyroscope = NO;
voidController.hasAccelerometer = NO;
voidController.reportRateHz = reportRateHz;
}
voidController.reportRateHz = reportRateHz;
voidController.controllerNumber = controllerNumber;
if(voidController == _oscController) [self updateTimerStateForController:voidController];
if (reportRateHz == 0) {
[self stopTimerForController:voidController];
}
else if(voidController == _oscController) {
[self updateTimerStateForController:voidController];
}
//}
}
@@ -605,6 +691,62 @@ static void ApplyAdaptiveTriggerEffect(GCDualSenseAdaptiveTrigger* trigger,
}
}
-(BOOL) hasDualSenseController:(uint16_t)controllerNumber {
__block BOOL result = NO;
dispatch_block_t checkBlock = ^{
if (@available(iOS 14.5, tvOS 14.5, *)) {
VoidController* controller = [self->_voidControllers objectForKey:@(controllerNumber)];
result = controller != nil &&
[controller.gamepad.extendedGamepad isKindOfClass:[GCDualSenseGamepad class]];
}
};
if ([NSThread isMainThread]) {
checkBlock();
}
else {
dispatch_sync(dispatch_get_main_queue(), checkBlock);
}
return result;
}
-(void) renderDeviceDualSenseHaptics:(uint16_t)controllerNumber
leftAmplitude:(float)leftAmplitude
leftSharpness:(float)leftSharpness
leftTransient:(float)leftTransient
rightAmplitude:(float)rightAmplitude
rightSharpness:(float)rightSharpness
rightTransient:(float)rightTransient
delaySeconds:(double)delaySeconds {
__block uint64_t generation;
@synchronized(self) {
generation = _authoredHapticsGeneration;
}
dispatch_block_t renderBlock = ^{
@synchronized(self) {
if (generation != self->_authoredHapticsGeneration) {
return;
}
}
if (@available(iOS 14.0, tvOS 14.0, *)) {
[self->_oscController.lowFreqMotor setAuthoredAmplitude:leftAmplitude
sharpness:leftSharpness
transientStrength:leftTransient];
[self->_oscController.highFreqMotor setAuthoredAmplitude:rightAmplitude
sharpness:rightSharpness
transientStrength:rightTransient];
}
};
if (delaySeconds > 0.0) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delaySeconds * NSEC_PER_SEC)),
dispatch_get_main_queue(), renderBlock);
}
else {
dispatch_async(dispatch_get_main_queue(), renderBlock);
}
}
-(void) cancelScheduledDualSenseHaptics {
@synchronized(self) {
_authoredHapticsGeneration++;
@@ -1020,7 +1162,8 @@ static void ApplyAdaptiveTriggerEffect(GCDualSenseAdaptiveTrigger* trigger,
// LI_CTYPE_UNKNOWN for option "Both"
if(voidController.playerIndex == 0){
type = _streamConfig.emulatedControllerType == LI_CTYPE_UNKNOWN ? LI_CTYPE_PS : _streamConfig.emulatedControllerType;
type = _streamConfig.emulatedControllerType == ControllerEmulationXboxAndPs ? LI_CTYPE_PS : _streamConfig.emulatedControllerType;
if(type == ControllerEmulationPsEnhancedHaptic) type = ControllerEmulationPs;
}
if(voidController.playerIndex == 1 && _streamConfig.emulatedControllerType == LI_CTYPE_UNKNOWN){
@@ -1038,15 +1181,10 @@ static void ApplyAdaptiveTriggerEffect(GCDualSenseAdaptiveTrigger* trigger,
if (@available(iOS 14.5, tvOS 14.5, *)) {
if ([controller.extendedGamepad isKindOfClass:[GCDualSenseGamepad class]]) {
type = LI_CTYPE_PS;
capabilities |= LI_CCAP_DS5_HAPTICS_PCM;
}
}
}
if (@available(iOS 14.5, tvOS 14.5, *)) {
if ([controller.extendedGamepad isKindOfClass:[GCDualSenseGamepad class]]) {
capabilities |= LI_CCAP_DS5_HAPTICS_PCM;
}
}
// Detect supported haptics localities
if (controller.haptics) {
@@ -1081,7 +1219,8 @@ static void ApplyAdaptiveTriggerEffect(GCDualSenseAdaptiveTrigger* trigger,
}
bool controllerLacksGyro = (capabilities & LI_CCAP_GYRO) == 0;
if(_streamConfig.emulatedControllerType == LI_CTYPE_PS && (_gyroMode == AlwaysDevice || (_gyroMode == GyroModeAuto && controllerLacksGyro)))
if((_streamConfig.emulatedControllerType == ControllerEmulationPs || _streamConfig.emulatedControllerType == ControllerEmulationPsEnhancedHaptic)
&& (_gyroMode == AlwaysDevice || (_gyroMode == GyroModeAuto && controllerLacksGyro)))
{
type = LI_CTYPE_PS;
capabilities |= LI_CCAP_GYRO | LI_CCAP_ACCEL;
@@ -1092,7 +1231,7 @@ static void ApplyAdaptiveTriggerEffect(GCDualSenseAdaptiveTrigger* trigger,
// This is a virtual controller corresponding to our OSC
// set osc to PS to utilize built-in gyro when "both" is selected
type = _streamConfig.emulatedControllerType == LI_CTYPE_UNKNOWN ? LI_CTYPE_PS : _streamConfig.emulatedControllerType;
if(type == ControllerEmulationPsEnhancedHaptic) type = LI_CTYPE_PS;
/*
if (_streamConfig.gyroMode != GyroModeOff) {
type = LI_CTYPE_PS;
@@ -1180,8 +1319,7 @@ double rc_expo(double x, double expo) {
- (bool)useMotionHandler{
return self->tempSettings.emulatedControllerType.intValue!=LI_CTYPE_PS
||self->tempSettings.gyroMode.intValue==GyroModeOff;
return true;
}
- (void)switchMotionControlOnOffByControllerButton{
@@ -1190,7 +1328,7 @@ double rc_expo(double x, double expo) {
if(self->_gyroEnabledFlag) [self->motionHandler startMotionControlByControllerButton];
else [self->motionHandler stopMotionUpdateWithInterruptNoneGyroInput:false];
}
else self->_gyroEnabledFlag = self->_gyroEnabledFlag || !self->_controllerGyroSwitchEnabled;
self->_gyroEnabledFlag = self->_gyroEnabledFlag || !self->_controllerGyroSwitchEnabled;
}
- (void)sendNavigationButtonPress {
@@ -1330,6 +1468,7 @@ double rc_expo(double x, double expo) {
*/
if([self useMotionHandler]
&& self->motionHandler.motionControlStarted
&& self->oscProfile.mapGyroTo==mapGyroToControllerStick
&& self->oscProfile.yawPitchToRightStick
&& self->_gyroEnabledFlag
@@ -1339,6 +1478,7 @@ double rc_expo(double x, double expo) {
else [self updateRightStick: voidController.playerIndex==0?self->_oscController:voidController x:rightStickX y:rightStickY];
if([self useMotionHandler]
&& self->motionHandler.motionControlStarted
&& self->oscProfile.mapGyroTo==mapGyroToControllerStick
&& self->oscProfile.rollToLeftStick
&& self->_gyroEnabledFlag
@@ -1611,7 +1751,7 @@ double rc_expo(double x, double expo) {
voidController.hasGyroscope = NO;
if(voidController.gamepad.motion.hasAttitudeAndRotationRate){
if([ControllerUtil hasControllerAccelerometer:voidController.gamepad]){
[voidController.motionTypes addObject:@(LI_MOTION_TYPE_ACCEL)];
voidController.hasAccelerometer = YES;
voidController.reportRateHz = 120;
@@ -1848,7 +1988,7 @@ double rc_expo(double x, double expo) {
NSLog(@"controller obj in dict: %@", controller);
}
if([self gamepadGyroEnabledInSetting]) [self updateFinished:_oscController];
if([self psGyroEnabledInSetting]) [self updateFinished:_oscController];
}
-(id)initWithConfig:(StreamConfiguration*)streamConfig delegate:(id<ControllerSupportDelegate>)delegate
@@ -2038,8 +2178,12 @@ double rc_expo(double x, double expo) {
return false;
}
-(bool)gamepadGyroEnabledInSetting {
return (tempSettings.emulatedControllerType.intValue == LI_CTYPE_PS && tempSettings.gyroMode.intValue != GyroModeOff);
-(bool)psGyroEnabledInSetting {
uint8_t emulation = tempSettings.emulatedControllerType.intValue;
return (emulation == ControllerEmulationPs
|| emulation == ControllerEmulationPsEnhancedHaptic
|| emulation == ControllerEmulationXboxAndPs)
&& tempSettings.gyroMode.intValue != GyroModeOff;
}
-(void)connectionEstablished {
@@ -2049,7 +2193,7 @@ double rc_expo(double x, double expo) {
//if (_oscEnabled
// || (tempSettings.emulatedControllerType.intValue == LI_CTYPE_PS && tempSettings.gyroMode != GyroModeOff)) {
if ([self gamepadGyroEnabledInSetting]) {
if ([self psGyroEnabledInSetting]) {
[self setButtonFlag:self->_oscController flags:A_FLAG];
[self updateFinished:self->_oscController];
[self clearButtonFlag:self->_oscController flags:A_FLAG];
@@ -2068,14 +2212,14 @@ double rc_expo(double x, double expo) {
[self resetGyroInputForController:voidController];
if (@available(iOS 14.0, *)) {
//NSLog(@"stop controller obj: %@, hasAcc %d, hasGyro %d", voidController, voidController.hasAccelerometer, voidController.hasGyroscope);
if(voidController.hasAccelerometer){
// if(voidController.hasAccelerometer){
[voidController.accelTimer invalidate];
voidController.accelTimer = nil;
}
if(voidController.hasGyroscope){
// }
// if(voidController.hasGyroscope){
[voidController.gyroTimer invalidate];
voidController.gyroTimer = nil;
}
// }
}
}
@@ -2083,11 +2227,12 @@ double rc_expo(double x, double expo) {
// Stop all timers to ensure a clean slate before applying the new setting.
[self stopTimerForAllControllers];
_gyroEnabledFlag = _gyroMode != GyroModeOff && tempSettings.emulatedControllerType.intValue == LI_CTYPE_PS;
_gyroEnabledFlag = [self psGyroEnabledInSetting];
switch(_gyroMode) {
case AlwaysController:
// Activate timers only for physical controllers.
for (VoidController* voidController in _voidControllers.allValues) {
if(voidController == _oscController) break;
[self updateTimerStateForController:voidController];
}
break;
@@ -2170,7 +2315,8 @@ double rc_expo(double x, double expo) {
}
}
-(void) dealloc
- (void) dealloc
{
if (VLSharedControllerSupport == self) {
VLSharedControllerSupport = nil;
+478 -44
View File
@@ -260,6 +260,13 @@ import UIKit
}
}
@objc enum ControllerEmulation: UInt8 {
case xbox = 0x01 // LI_CTYPE_XBOX
case ps = 0x02 // LI_CTYPE_PS
case psEnhancedHaptic = 0xFE
case xboxAndPs = 0x00 // LI_CTYPE_UNKNOWN
}
@objc protocol ControllerUtilDelegate: AnyObject {
func isStreaming() -> Bool
func isInAppView() -> Bool
@@ -269,6 +276,38 @@ import UIKit
@objc static weak var delegate: ControllerUtilDelegate?
#if !VOIDLINK_PREVIEW
@objc static var hasDualSenseController: Bool {
if #available(iOS 14.5, *) {
for gcController in GCController.controllers() {
if gcController.extendedGamepad is GCDualSenseGamepad {return true}
}
return false
}
else {return false}
}
@objc static func hasControllerAccelerometer(_ controller: GCController) -> Bool {
guard let motion = controller.motion else { return false }
if #available(iOS 14.0, tvOS 14.0, *) {
if motion.hasGravityAndUserAcceleration {
return true
}
if controller.extendedGamepad is GCDualShockGamepad {
return true
}
if #available(iOS 14.5, tvOS 14.5, *) {
if controller.extendedGamepad is GCDualSenseGamepad {
return true
}
}
}
return false
}
private struct DualSensePCMChunk {
let controllerNumber: UInt16
let flags: UInt8
@@ -310,17 +349,57 @@ import UIKit
}
}
private final class AuthoredIrRenderState {
var renderHostAnchorUs: UInt64?
var renderLocalAnchor = 0.0
var diagnosticFrameCounter: UInt32 = 0
func renderDelay(for timestampUs: UInt64, resetTimeline: Bool) -> TimeInterval {
let now = ProcessInfo.processInfo.systemUptime
if resetTimeline || renderHostAnchorUs == nil {
renderHostAnchorUs = timestampUs
renderLocalAnchor = now + 0.002
}
let anchor = renderHostAnchorUs ?? timestampUs
let hostDelta = timestampUs >= anchor ? Double(timestampUs - anchor) / 1_000_000.0 : 0
return min(max(renderLocalAnchor + hostDelta - now, 0), 0.025)
}
}
private static let dualSenseHapticsLock = NSLock()
private static let dualSenseHapticsWorker = DispatchQueue(
label: "com.voidlink.dualsense-authored-haptics",
qos: .userInteractive
)
private static let maxQueuedDualSensePCMChunks = 8
private static let defaultDualSenseHapticsEQ: [Float] = [0.78, 0.98, 1.59, 0.175, 1.2]
private static let defaultDualSenseControllerHapticsEQ: [Float] = [0.78, 0.98, 1.59, 0.175, 1.2]
private static let defaultDeviceDualSenseHapticsEQ: [Float] = [18, 0.98, 1.59, 0.9, 1.2]
private static let dualSenseControllerAmplitudeScale: Float = 1.15
private static let dualSenseControllerTransientScale: Float = 1.0
private static let dualSenseControllerSharpnessScale: Float = 1.15
private static let dualSenseControllerMirrorMixedHandles = false
private static var dualSensePCMQueue = Deque<DualSensePCMChunk>()
private static var dualSenseWorkerScheduled = false
private static var dualSenseForcedDiscontinuities = Set<UInt16>()
private static var dualSenseAuthoredEngines = [UInt16: AuthoredEngineState]()
private static var dualSenseIrRenderStates = [UInt16: AuthoredIrRenderState]()
private static let authoredHapticsRouteLock = NSLock()
private static var authoredHapticsPreference = AuthoredHapticsPreference.auto
private static var authoredHapticsLastRoutedToDualSense = false
private static var authoredHapticsLastRoute = AuthoredHapticsRoute.off
private enum AuthoredHapticsPreference: Int {
case auto = 0
case device = 1
case swapped = 2
case off = 3
}
private enum AuthoredHapticsRoute {
case dualSense
case device
case off
}
#if os(iOS)
private static var dualSenseHapticsEQOverlayWindow: UIWindow?
@@ -329,11 +408,70 @@ import UIKit
#endif
fileprivate static func dualSenseHapticsEQSnapshot() -> [Float] {
defaultDualSenseHapticsEQ
defaultDeviceDualSenseHapticsEQ
}
fileprivate static func defaultDualSenseHapticsEQSnapshot() -> [Float] {
defaultDualSenseHapticsEQ
defaultDeviceDualSenseHapticsEQ
}
private static func dualSenseHapticsEQSnapshot(for route: AuthoredHapticsRoute) -> [Float] {
switch route {
case .dualSense:
return defaultDualSenseControllerHapticsEQ
case .device, .off:
return defaultDeviceDualSenseHapticsEQ
}
}
private static func compensatedDualSenseControllerValues(
_ values: (amplitude: Float, sharpness: Float, transient: Float),
highBandRatio: Float
) -> (amplitude: Float, sharpness: Float, transient: Float) {
let lowFrequencyTrim = 0.82 + 0.18 * min(max(highBandRatio, 0), 1)
let amplitude = min(max(
pow(values.amplitude, 0.50) * dualSenseControllerAmplitudeScale * lowFrequencyTrim,
0
), 1)
let sharpness = min(max(
pow(values.sharpness, 0.8) * dualSenseControllerSharpnessScale,
0
), 1)
let transient = min(max(
pow(values.transient, 0.9) * dualSenseControllerTransientScale,
0
), 1)
return (amplitude, sharpness, transient)
}
private static func authoredHighBandRatio(for lane: AhAuthoredLaneFrame?) -> Float {
1 - min(max(lane?.low_band_ratio ?? 1, 0), 1)
}
private static func mixedDualSenseControllerHandleValues(
left: (amplitude: Float, sharpness: Float, transient: Float),
right: (amplitude: Float, sharpness: Float, transient: Float)
) -> (
left: (amplitude: Float, sharpness: Float, transient: Float),
right: (amplitude: Float, sharpness: Float, transient: Float)
) {
guard dualSenseControllerMirrorMixedHandles else {
return (left, right)
}
let amplitude = min(max(sqrt(
(left.amplitude * left.amplitude + right.amplitude * right.amplitude) * 0.5
), 0), 1)
let amplitudeSum = left.amplitude + right.amplitude
let sharpness = amplitudeSum > 0.000_001
? min(max(
(left.sharpness * left.amplitude + right.sharpness * right.amplitude) / amplitudeSum,
0
), 1)
: min(max(max(left.sharpness, right.sharpness), 0), 1)
let transient = min(max(max(left.transient, right.transient), 0), 1)
let mixed = (amplitude: amplitude, sharpness: sharpness, transient: transient)
return (mixed, mixed)
}
fileprivate static func previewDualSenseHapticsEQ(_ values: [Float]) {
@@ -342,6 +480,86 @@ import UIKit
fileprivate static func saveDualSenseHapticsEQ(_ values: [Float]) {
}
private static func refreshAuthoredHapticsPreference() -> AuthoredHapticsPreference {
let rawValue = Int(DataManager().getSettings().hapticEngine.intValue)
let preference = AuthoredHapticsPreference(rawValue: rawValue) ?? .auto
authoredHapticsRouteLock.lock()
authoredHapticsPreference = preference
authoredHapticsRouteLock.unlock()
return preference
}
private static func selectAuthoredHapticsRoute(
controllerNumber: UInt16,
controllerSupport: ControllerSupport
) -> AuthoredHapticsRoute {
switch refreshAuthoredHapticsPreference() {
case .off:
return .off
case .device:
return .device
case .auto, .swapped:
let hasDualSense = controllerSupport.hasDualSenseController(controllerNumber)
authoredHapticsRouteLock.lock()
authoredHapticsLastRoutedToDualSense = hasDualSense
authoredHapticsRouteLock.unlock()
return hasDualSense ? .dualSense : .device
}
}
private static func stopAuthoredHapticsRoute(
_ route: AuthoredHapticsRoute,
controllerNumber: UInt16,
controllerSupport: ControllerSupport
) {
switch route {
case .dualSense:
controllerSupport.renderDualSenseHaptics(
controllerNumber,
leftAmplitude: 0,
leftSharpness: 0,
leftTransient: 0,
rightAmplitude: 0,
rightSharpness: 0,
rightTransient: 0,
delaySeconds: 0
)
case .device:
controllerSupport.renderDeviceDualSenseHaptics(
controllerNumber,
leftAmplitude: 0,
leftSharpness: 0,
leftTransient: 0,
rightAmplitude: 0,
rightSharpness: 0,
rightTransient: 0,
delaySeconds: 0
)
case .off:
break
}
}
private static func updateAuthoredHapticsRouteTransition(
to route: AuthoredHapticsRoute,
controllerNumber: UInt16,
controllerSupport: ControllerSupport
) {
authoredHapticsRouteLock.lock()
let previousRoute = authoredHapticsLastRoute
authoredHapticsLastRoute = route
authoredHapticsLastRoutedToDualSense = route == .dualSense
authoredHapticsRouteLock.unlock()
if previousRoute != route {
stopAuthoredHapticsRoute(
previousRoute,
controllerNumber: controllerNumber,
controllerSupport: controllerSupport
)
}
}
#if os(iOS)
@available(iOS 14.0, *)
@objc static func presentDualSenseHapticsEQIfNeeded() {
@@ -425,6 +643,51 @@ import UIKit
}
}
@objc(enqueueDualSenseHapticsIRV2WithControllerNumber:flags:sourceSequenceNumber:timestampUs:sourceFrameCount:leftRms:leftPeak:leftTransient:leftLowRatio:leftZeroCrossHz:rightRms:rightPeak:rightTransient:rightLowRatio:rightZeroCrossHz:laneCorrelation:)
static func enqueueDualSenseHapticsIRV2(
controllerNumber: UInt16,
flags: UInt8,
sourceSequenceNumber: UInt32,
timestampUs: UInt64,
sourceFrameCount: UInt32,
leftRms: Float,
leftPeak: Float,
leftTransient: Float,
leftLowRatio: Float,
leftZeroCrossHz: Float,
rightRms: Float,
rightPeak: Float,
rightTransient: Float,
rightLowRatio: Float,
rightZeroCrossHz: Float,
laneCorrelation: Float
) {
dualSenseHapticsWorker.async {
processDualSenseIRV2Frame(
controllerNumber: controllerNumber,
flags: flags,
sourceSequenceNumber: sourceSequenceNumber,
timestampUs: timestampUs,
sourceFrameCount: sourceFrameCount,
left: makeAuthoredLaneFrame(
rmsAmplitude: leftRms,
peakAmplitude: leftPeak,
transientStrength: leftTransient,
lowBandRatio: leftLowRatio,
zeroCrossingRateHz: leftZeroCrossHz
),
right: makeAuthoredLaneFrame(
rmsAmplitude: rightRms,
peakAmplitude: rightPeak,
transientStrength: rightTransient,
lowBandRatio: rightLowRatio,
zeroCrossingRateHz: rightZeroCrossHz
),
laneCorrelation: laneCorrelation
)
}
}
@objc static func stopAllDualSenseHaptics() {
ControllerSupport.sharedInstance()?.cancelScheduledDualSenseHaptics()
@@ -434,14 +697,79 @@ import UIKit
dualSenseHapticsLock.unlock()
dualSenseHapticsWorker.async {
let controllerNumbers = Array(dualSenseAuthoredEngines.keys)
let controllerNumbers = Array(
Set(dualSenseAuthoredEngines.keys).union(dualSenseIrRenderStates.keys)
)
dualSenseAuthoredEngines.removeAll(keepingCapacity: false)
dualSenseIrRenderStates.removeAll(keepingCapacity: false)
for controllerNumber in controllerNumbers {
renderDualSenseHaptics(controllerNumber: controllerNumber, left: nil, right: nil, delay: 0)
}
}
}
private static func makeAuthoredLaneFrame(
rmsAmplitude: Float,
peakAmplitude: Float,
transientStrength: Float,
lowBandRatio: Float,
zeroCrossingRateHz: Float
) -> AhAuthoredLaneFrame {
var lane = AhAuthoredLaneFrame()
lane.rms_amplitude = rmsAmplitude
lane.peak_amplitude = peakAmplitude
lane.transient_strength = transientStrength
lane.low_band_ratio = lowBandRatio
lane.zero_crossing_rate_hz = zeroCrossingRateHz
return lane
}
private static func processDualSenseIRV2Frame(
controllerNumber: UInt16,
flags: UInt8,
sourceSequenceNumber: UInt32,
timestampUs: UInt64,
sourceFrameCount: UInt32,
left: AhAuthoredLaneFrame,
right: AhAuthoredLaneFrame,
laneCorrelation: Float
) {
let isDiscontinuity = (flags & 0x01) != 0
let isStreamEnd = (flags & 0x04) != 0
let isSilent = (flags & 0x08) != 0
let state = dualSenseIrRenderStates[controllerNumber] ?? AuthoredIrRenderState()
dualSenseIrRenderStates[controllerNumber] = state
let delay = state.renderDelay(
for: timestampUs,
resetTimeline: isDiscontinuity || state.diagnosticFrameCounter == 0
)
state.diagnosticFrameCounter &+= 1
renderDualSenseHaptics(
controllerNumber: controllerNumber,
left: isSilent ? nil : left,
right: isSilent ? nil : right,
laneCorrelation: laneCorrelation,
logDiagnostics: state.diagnosticFrameCounter % 100 == 0,
appliesEQ: false,
delay: delay
)
if isStreamEnd {
renderDualSenseHaptics(
controllerNumber: controllerNumber,
left: nil,
right: nil,
appliesEQ: false,
delay: delay + 0.005
)
dualSenseIrRenderStates.removeValue(forKey: controllerNumber)
}
_ = sourceSequenceNumber
_ = sourceFrameCount
}
private static func drainDualSensePCMQueue() {
while true {
dualSenseHapticsLock.lock()
@@ -544,6 +872,7 @@ import UIKit
right: silent ? nil : frame.lanes.1,
laneCorrelation: frame.lane_correlation,
logDiagnostics: state.diagnosticFrameCounter % 100 == 0,
appliesEQ: false,
delay: delay
)
}
@@ -560,26 +889,52 @@ import UIKit
}
}
@objc static var dualSenseHapticTransient: Float = 1.0
private static func renderDualSenseHaptics(
controllerNumber: UInt16,
left: AhAuthoredLaneFrame?,
right: AhAuthoredLaneFrame?,
laneCorrelation: Float = 0,
logDiagnostics: Bool = false,
appliesEQ: Bool = true,
delay: TimeInterval
) {
guard let controllerSupport = ControllerSupport.sharedInstance() else {
return
}
let eq = dualSenseHapticsEQSnapshot()
let route = selectAuthoredHapticsRoute(
controllerNumber: controllerNumber,
controllerSupport: controllerSupport
)
updateAuthoredHapticsRouteTransition(
to: route,
controllerNumber: controllerNumber,
controllerSupport: controllerSupport
)
guard route != .off else {
return
}
let eq = appliesEQ ? dualSenseHapticsEQSnapshot(for: route) : [1, 1, 1, 1, 1] as [Float]
let masterGain = eq[0]
let lowBandGain = eq[1]
let highBandGain = eq[2]
let transientGain = eq[3]
let sharpnessGain = eq[4]
func renderValues(for lane: AhAuthoredLaneFrame?) -> (amplitude: Float, sharpness: Float, transient: Float) {
func rawIrValues(for lane: AhAuthoredLaneFrame?) -> (amplitude: Float, sharpness: Float, transient: Float) {
guard let lane else {
return (0, 0, 0)
}
let amplitude = min(max(lane.rms_amplitude, 0), 1)
let sharpness = min(max(1 - lane.low_band_ratio, 0), 1)
let transient = min(max(lane.transient_strength, 0), 1)
return (amplitude, sharpness, transient)
}
func renderValues1(for lane: AhAuthoredLaneFrame?) -> (amplitude: Float, sharpness: Float, transient: Float) {
let lowBandRatio = min(max(lane?.low_band_ratio ?? 1, 0), 1)
let highBandRatio = 1 - lowBandRatio
let effectiveLowBandGain = pow(lowBandGain, 1.6)
@@ -604,10 +959,12 @@ import UIKit
: immediateSharpness
let sourceTransient = min(max(lane?.transient_strength ?? 0, 0), 1)
let rmsAmplitude = min(max(lane?.rms_amplitude ?? 0, 0), 1)
let perceptualBandTrim = 0.55 + 0.55 * equalizedHighBandRatio
let transientBandTrim = 0.55 + 0.35 * lowBandRatio + 0.10 * equalizedHighBandRatio
let lowContinuousTrim = 0.78 + 0.22 * lowBandRatio
let highContinuousTrim = 0.62 - 0.30 * equalizedHighBandRatio
let renderedTransient = min(max(
pow(sourceTransient, 0.45) * sqrt(masterGain) * transientGain *
bandGain * perceptualBandTrim * 1.35,
bandGain * transientBandTrim * 1.35,
0
), 1)
let continuousShare = 1 - 0.85 * min(
@@ -617,11 +974,11 @@ import UIKit
let lowBody = pow(rmsAmplitude, 1.85) *
(1 - highBandRatio) * effectiveLowBandGain * 0.55
let highTexture = pow(rmsAmplitude, 0.55) *
pow(highBandRatio, 1.15) * effectiveHighBandGain * 0.22
pow(highBandRatio, 1.35) * effectiveHighBandGain * 0.09
let midBodyTrim = 1 - 0.55 * min(max((representativeFrequency - 90) / 140, 0), 1) *
(1 - highBandRatio)
let continuousAmplitude = (lowBody + highTexture) * masterGain *
perceptualBandTrim * midBodyTrim * continuousShare
let continuousAmplitude = (lowBody * lowContinuousTrim + highTexture * highContinuousTrim) *
masterGain * midBodyTrim * continuousShare
return (
min(max(continuousAmplitude, 0), 1),
min(max(synthesizedSharpness * sharpnessGain, 0), 1),
@@ -629,36 +986,83 @@ import UIKit
)
}
var leftValues = renderValues(for: left)
var rightValues = renderValues(for: right)
func renderValues2(for lane: AhAuthoredLaneFrame?) -> (amplitude: Float, sharpness: Float, transient: Float) {
guard let lane else {
return (0, 0, 0)
}
let positiveCorrelation = min(max(laneCorrelation, 0), 1)
let centerBlend = min(max((positiveCorrelation - 0.1) / 0.9, 0), 1) * 0.85
if centerBlend > 0 {
let commonAmplitude = sqrt(
(leftValues.amplitude * leftValues.amplitude +
rightValues.amplitude * rightValues.amplitude) * 0.5
let useBuiltinHaptic = route == .device
let rmsAmplitude = min(max(lane.rms_amplitude, 0), 1)
let lowBandRatio = min(max(lane.low_band_ratio, 0), 1)
let highBandRatio = 1 - lowBandRatio
let amplitudeLowBandGain: Float = 1.0
let amplitudeHighBandGain: Float = 1.0
let bandGain = lowBandRatio * amplitudeLowBandGain + highBandRatio * amplitudeHighBandGain
let sourceSharpness = min(max(highBandRatio, 0), 1)
let sharpnessLowGain: Float = 0.62
let sharpnessMidGain: Float = 0.82
let sharpnessHighGain: Float = 1.2
let sharpnessLowWeight = min(max((0.50 - sourceSharpness) / 0.50, 0), 1)
let sharpnessHighWeight = min(max((sourceSharpness - 0.50) / 0.50, 0), 1)
let sharpnessMidWeight = 1 - max(sharpnessLowWeight, sharpnessHighWeight)
let sharpnessGain = sharpnessLowWeight * sharpnessLowGain +
sharpnessMidWeight * sharpnessMidGain +
sharpnessHighWeight * sharpnessHighGain
let sharpness = min(max(sourceSharpness * sharpnessGain, 0), 1)
let transient = min(max(lane.transient_strength, 0), 1)
let amplitudeCeiling: Float = 1.0
let transientGain: Float = useBuiltinHaptic ? 1.03 : 0.343
let transientCeiling: Float = useBuiltinHaptic ? 1.03 : 0.6
let transientBottom: Float = useBuiltinHaptic ? 0.14 : 0.0
return (
min(rmsAmplitude * bandGain, amplitudeCeiling),
sharpness,
min(max(transientBottom, transient * transientGain * 1.6666666667 * dualSenseHapticTransient), transientCeiling)
)
let amplitudeSum = leftValues.amplitude + rightValues.amplitude
let commonSharpness = amplitudeSum > 0.000_001
? (leftValues.sharpness * leftValues.amplitude +
rightValues.sharpness * rightValues.amplitude) / amplitudeSum
: max(leftValues.sharpness, rightValues.sharpness)
let commonTransient = max(leftValues.transient, rightValues.transient)
leftValues.amplitude += (commonAmplitude - leftValues.amplitude) * centerBlend
rightValues.amplitude += (commonAmplitude - rightValues.amplitude) * centerBlend
leftValues.sharpness += (commonSharpness - leftValues.sharpness) * centerBlend
rightValues.sharpness += (commonSharpness - rightValues.sharpness) * centerBlend
leftValues.transient += (commonTransient - leftValues.transient) * centerBlend
rightValues.transient += (commonTransient - rightValues.transient) * centerBlend
}
var leftValues = appliesEQ ? renderValues1(for: left) : renderValues2(for: left)
var rightValues = appliesEQ ? renderValues1(for: right) : renderValues2(for: right)
if appliesEQ {
let positiveCorrelation = min(max(laneCorrelation, 0), 1)
let commonHighBandRatio = (
authoredHighBandRatio(for: left) + authoredHighBandRatio(for: right)
) * 0.5
let dualSenseCenterBlendScale = 0.10 + 0.55 * commonHighBandRatio
let centerBlendScale: Float = route == .dualSense ? dualSenseCenterBlendScale : 0.85
let centerBlend = min(max((positiveCorrelation - 0.1) / 0.9, 0), 1) * centerBlendScale
if centerBlend > 0 {
let commonAmplitude = sqrt(
(leftValues.amplitude * leftValues.amplitude +
rightValues.amplitude * rightValues.amplitude) * 0.5
)
let amplitudeSum = leftValues.amplitude + rightValues.amplitude
let commonSharpness = amplitudeSum > 0.000_001
? (leftValues.sharpness * leftValues.amplitude +
rightValues.sharpness * rightValues.amplitude) / amplitudeSum
: max(leftValues.sharpness, rightValues.sharpness)
let commonTransient = max(leftValues.transient, rightValues.transient)
leftValues.amplitude += (commonAmplitude - leftValues.amplitude) * centerBlend
rightValues.amplitude += (commonAmplitude - rightValues.amplitude) * centerBlend
leftValues.sharpness += (commonSharpness - leftValues.sharpness) * centerBlend
rightValues.sharpness += (commonSharpness - rightValues.sharpness) * centerBlend
leftValues.transient += (commonTransient - leftValues.transient) * centerBlend
rightValues.transient += (commonTransient - rightValues.transient) * centerBlend
}
}
/*
if logDiagnostics {
NSLog(
"DS5 authored: c=%u srcL[rms=%.3f high=%.3f hz=%.0f] srcR[rms=%.3f high=%.3f hz=%.0f] corr=%.2f outL[a=%.3f s=%.3f t=%.3f] outR[a=%.3f s=%.3f t=%.3f]",
"DS5 authored: c=%u route=%@ srcL[rms=%.3f high=%.3f hz=%.0f] srcR[rms=%.3f high=%.3f hz=%.0f] corr=%.2f outL[a=%.3f s=%.3f t=%.3f] outR[a=%.3f s=%.3f t=%.3f]",
controllerNumber,
route == .dualSense ? "dualsense" : "device",
Double(left?.rms_amplitude ?? 0),
Double(1 - min(max(left?.low_band_ratio ?? 1, 0), 1)),
Double(min(max((left?.zero_crossing_rate_hz ?? 0) * 0.5, 0), 400)),
@@ -674,18 +1078,47 @@ import UIKit
Double(rightValues.transient)
)
}
*/
*/
controllerSupport.renderDualSenseHaptics(
controllerNumber,
leftAmplitude: leftValues.amplitude,
leftSharpness: leftValues.sharpness,
leftTransient: leftValues.transient,
rightAmplitude: rightValues.amplitude,
rightSharpness: rightValues.sharpness,
rightTransient: rightValues.transient,
delaySeconds: delay
)
switch route {
case .dualSense:
if appliesEQ {
leftValues = compensatedDualSenseControllerValues(
leftValues,
highBandRatio: authoredHighBandRatio(for: left)
)
rightValues = compensatedDualSenseControllerValues(
rightValues,
highBandRatio: authoredHighBandRatio(for: right)
)
let mixedValues = mixedDualSenseControllerHandleValues(left: leftValues, right: rightValues)
leftValues = mixedValues.left
rightValues = mixedValues.right
}
controllerSupport.renderDualSenseHaptics(
controllerNumber,
leftAmplitude: leftValues.amplitude,
leftSharpness: leftValues.sharpness,
leftTransient: leftValues.transient,
rightAmplitude: rightValues.amplitude,
rightSharpness: rightValues.sharpness,
rightTransient: rightValues.transient,
delaySeconds: delay
)
case .device:
controllerSupport.renderDeviceDualSenseHaptics(
controllerNumber,
leftAmplitude: leftValues.amplitude,
leftSharpness: leftValues.sharpness,
leftTransient: leftValues.transient,
rightAmplitude: rightValues.amplitude,
rightSharpness: rightValues.sharpness,
rightTransient: rightValues.transient,
delaySeconds: delay
)
case .off:
break
}
}
#endif
@@ -711,6 +1144,7 @@ import UIKit
let elementDict = tempMap as NSDictionary
// 单一 gamepad.valueChangedHandler
gamepad.valueChangedHandler = { gamepad, element in
handler(elementDict, gamepad, element)
if #available(iOS 13.0, *) {
+1
View File
@@ -65,6 +65,7 @@ typedef NS_ENUM(NSInteger, PencilAndHoverMode) {
@property (nonatomic, assign) CGFloat touchPointerVelocityFactor;
@property (nonatomic, assign) CGPoint normalizedStreamViewOffset;
@property (nonatomic, assign) CGFloat streamViewScale;
@property (nonatomic, assign) CGFloat dualSenseTransient;
@property (nonatomic, assign) bool gamepadOverlayEnabled;
+3 -1
View File
@@ -58,6 +58,7 @@
[encoder encodeFloat:self.touchPointerVelocityFactor forKey:@"touchPointerVelocityFactor"];
[encoder encodeCGPoint:self.normalizedStreamViewOffset forKey:@"normalizedStreamViewOffset"];
[encoder encodeFloat:self.streamViewScale forKey:@"streamViewScale"];
[encoder encodeFloat:self.dualSenseTransient forKey:@"dualSenseTransient"];
[encoder encodeBool:self.gamepadOverlayEnabled forKey:@"gamepadOverlayEnabled"];
[encoder encodeObject:self.pressureCurvePoints forKey:@"pressureCurvePoints"];
@@ -132,7 +133,8 @@
self.normalizedStreamViewOffset = [decoder containsValueForKey:@"normalizedStreamViewOffset"] ? [decoder decodeCGPointForKey:@"normalizedStreamViewOffset"] : CGPointZero;
self.streamViewScale = [decoder containsValueForKey:@"streamViewScale"] ? [decoder decodeFloatForKey:@"streamViewScale"] : 1.0;
self.dualSenseTransient = [decoder containsValueForKey:@"dualSenseTransient"] ? [decoder decodeFloatForKey:@"dualSenseTransient"] : 1.0;
self.gamepadOverlayEnabled = [decoder containsValueForKey:@"gamepadOverlayEnabled"] ? [decoder decodeBoolForKey:@"gamepadOverlayEnabled"] : false;
self.pressureCurvePoints =
+48 -34
View File
@@ -18,14 +18,20 @@
@implementation HapticContext {
GCControllerPlayerIndex _playerIndex;
CHHapticEngine* _hapticEngine API_AVAILABLE(ios(13.0), tvos(14.0));
id<CHHapticPatternPlayer> _hapticPlayer API_AVAILABLE(ios(13.0), tvos(14.0));
BOOL _playing;
id<CHHapticPatternPlayer> _motorHapticPlayer API_AVAILABLE(ios(13.0), tvos(14.0));
id<CHHapticPatternPlayer> _authoredHapticPlayer API_AVAILABLE(ios(13.0), tvos(14.0));
BOOL _motorPlaying;
BOOL _authoredPlaying;
}
-(void)cleanup API_AVAILABLE(ios(14.0), tvos(14.0)) {
if (_hapticPlayer != nil) {
[_hapticPlayer cancelAndReturnError:nil];
_hapticPlayer = nil;
if (_motorHapticPlayer != nil) {
[_motorHapticPlayer cancelAndReturnError:nil];
_motorHapticPlayer = nil;
}
if (_authoredHapticPlayer != nil) {
[_authoredHapticPlayer cancelAndReturnError:nil];
_authoredHapticPlayer = nil;
}
if (_hapticEngine != nil) {
[_hapticEngine stopWithCompletionHandler:nil];
@@ -43,15 +49,15 @@
// Stop the effect entirely if the amplitude is 0
if (amplitude == 0) {
if (_playing) {
[_hapticPlayer stopAtTime:0 error:&error];
_playing = NO;
if (_motorPlaying) {
[_motorHapticPlayer stopAtTime:0 error:&error];
_motorPlaying = NO;
}
return;
}
if (_hapticPlayer == nil) {
if (_motorHapticPlayer == nil) {
// We must initialize the intensity to 1.0f because the dynamic parameters are multiplied by this value before being applied
CHHapticEventParameter* intensityParameter = [[CHHapticEventParameter alloc] initWithParameterID:CHHapticEventParameterIDHapticIntensity value:1.0f];
CHHapticEvent* hapticEvent = [[CHHapticEvent alloc] initWithEventType:CHHapticEventTypeHapticContinuous parameters:[NSArray arrayWithObject:intensityParameter] relativeTime:0 duration:GCHapticDurationInfinite];
@@ -61,7 +67,7 @@
return;
}
_hapticPlayer = [_hapticEngine createPlayerWithPattern:hapticPattern error:&error];
_motorHapticPlayer = [_hapticEngine createPlayerWithPattern:hapticPattern error:&error];
if (error != nil) {
Log(LOG_W, @"Controller %d: Haptic player creation failed: %@", _playerIndex, error);
return;
@@ -69,21 +75,21 @@
}
CHHapticDynamicParameter* intensityParameter = [[CHHapticDynamicParameter alloc] initWithParameterID:CHHapticDynamicParameterIDHapticIntensityControl value:amplitude / 65535.0f relativeTime:0];
[_hapticPlayer sendParameters:[NSArray arrayWithObject:intensityParameter] atTime:CHHapticTimeImmediate error:&error];
[_motorHapticPlayer sendParameters:[NSArray arrayWithObject:intensityParameter] atTime:CHHapticTimeImmediate error:&error];
if (error != nil) {
Log(LOG_W, @"Controller %d: Haptic player parameter update failed: %@", _playerIndex, error);
return;
}
if (!_playing) {
[_hapticPlayer startAtTime:0 error:&error];
if (!_motorPlaying) {
[_motorHapticPlayer startAtTime:0 error:&error];
if (error != nil) {
_hapticPlayer = nil;
_motorHapticPlayer = nil;
Log(LOG_W, @"Controller %d: Haptic playback start failed: %@", _playerIndex, error);
return;
}
_playing = YES;
_motorPlaying = YES;
}
}
@@ -100,13 +106,13 @@
NSError* error = nil;
if (amplitude == 0.0f) {
if (_playing) {
[_hapticPlayer stopAtTime:CHHapticTimeImmediate error:&error];
_playing = NO;
if (_authoredPlaying) {
[_authoredHapticPlayer stopAtTime:CHHapticTimeImmediate error:&error];
_authoredPlaying = NO;
}
}
else {
if (_hapticPlayer == nil) {
if (_authoredHapticPlayer == nil) {
CHHapticEventParameter* intensity = [[CHHapticEventParameter alloc]
initWithParameterID:CHHapticEventParameterIDHapticIntensity value:1.0f];
CHHapticEventParameter* eventSharpness = [[CHHapticEventParameter alloc]
@@ -122,8 +128,8 @@
Log(LOG_W, @"Controller %d: Authored haptic pattern creation failed: %@", _playerIndex, error);
return;
}
_hapticPlayer = [_hapticEngine createPlayerWithPattern:pattern error:&error];
if (_hapticPlayer == nil || error != nil) {
_authoredHapticPlayer = [_hapticEngine createPlayerWithPattern:pattern error:&error];
if (_authoredHapticPlayer == nil || error != nil) {
Log(LOG_W, @"Controller %d: Authored haptic player creation failed: %@", _playerIndex, error);
return;
}
@@ -137,19 +143,19 @@
initWithParameterID:CHHapticDynamicParameterIDHapticSharpnessControl
value:sharpness relativeTime:0]
];
[_hapticPlayer sendParameters:parameters atTime:CHHapticTimeImmediate error:&error];
[_authoredHapticPlayer sendParameters:parameters atTime:CHHapticTimeImmediate error:&error];
if (error != nil) {
Log(LOG_W, @"Controller %d: Authored haptic parameter update failed: %@", _playerIndex, error);
return;
}
if (!_playing) {
[_hapticPlayer startAtTime:CHHapticTimeImmediate error:&error];
if (!_authoredPlaying) {
[_authoredHapticPlayer startAtTime:CHHapticTimeImmediate error:&error];
if (error != nil) {
_hapticPlayer = nil;
_authoredHapticPlayer = nil;
Log(LOG_W, @"Controller %d: Authored haptic playback start failed: %@", _playerIndex, error);
return;
}
_playing = YES;
_authoredPlaying = YES;
}
}
@@ -197,9 +203,11 @@
}
Log(LOG_W, @"Controller %d: Haptic engine stopped: %p", me->_playerIndex, stoppedReason);
me->_hapticPlayer = nil;
me->_motorHapticPlayer = nil;
me->_authoredHapticPlayer = nil;
me->_hapticEngine = nil;
me->_playing = NO;
me->_motorPlaying = NO;
me->_authoredPlaying = NO;
};
_hapticEngine.resetHandler = ^{
HapticContext* me = weakSelf;
@@ -208,8 +216,10 @@
}
Log(LOG_W, @"Controller %d: Haptic engine reset", me->_playerIndex);
me->_hapticPlayer = nil;
me->_playing = NO;
me->_motorHapticPlayer = nil;
me->_authoredHapticPlayer = nil;
me->_motorPlaying = NO;
me->_authoredPlaying = NO;
[me->_hapticEngine startAndReturnError:nil];
};
@@ -258,9 +268,11 @@
}
Log(LOG_W, @"Controller %d: Haptic engine stopped: %p", me->_playerIndex, stoppedReason);
me->_hapticPlayer = nil;
me->_motorHapticPlayer = nil;
me->_authoredHapticPlayer = nil;
me->_hapticEngine = nil;
me->_playing = NO;
me->_motorPlaying = NO;
me->_authoredPlaying = NO;
};
_hapticEngine.resetHandler = ^{
HapticContext* me = weakSelf;
@@ -269,8 +281,10 @@
}
Log(LOG_W, @"Controller %d: Haptic engine reset", me->_playerIndex);
me->_hapticPlayer = nil;
me->_playing = NO;
me->_motorHapticPlayer = nil;
me->_authoredHapticPlayer = nil;
me->_motorPlaying = NO;
me->_authoredPlaying = NO;
[me->_hapticEngine startAndReturnError:nil];
};
+18 -7
View File
@@ -179,6 +179,9 @@ import CoreMotion
if useBuiltinGyro {
if motionManager.isGyroAvailable {
if rollToLeftStick {
leftStickMotion = leftStickTouchInputX
}
motionManager.startDeviceMotionUpdates(to: .main) { [weak self] motionData, _ in
guard let self = self, let data = motionData else { return }
self.handleMotionData(deviceMotion: data)
@@ -229,7 +232,7 @@ import CoreMotion
}
/// 停止更新
private var interruptNoneGyroInput:Bool = false
// private var interruptNoneGyroInput:Bool = false
@objc public func stopMotionUpdate(interruptNoneGyroInput:Bool=false) {
motionControlStarted = false
@@ -362,7 +365,7 @@ import CoreMotion
}
if !motionControlStarted {
self.clearGyroInput(interruptNonGyroInput: interruptNoneGyroInput)
self.clearGyroInput(interruptNonGyroInput: false)
// print("Gyro: stopped")
return
}
@@ -394,12 +397,12 @@ import CoreMotion
if rollToLeftStick {
roll = gyroInputToStickInput(input:rollSource*sensitvityRoll*widgetRollFactor*0.2)
leftStickMotion = useBuiltinGyro ? -stickMaxOffset*(gravityYOffset/Double.pi)*3*sensitvityRoll*widgetRollFactor : leftStickMotion + roll
leftStickMotion = self.clampStickInput(input: useBuiltinGyro ? -stickMaxOffset*(gravityYOffset/Double.pi)*3*sensitvityRoll*widgetRollFactor : leftStickMotion + roll)
let mixedLeftStickOffsetX = self.clampStickInput(input: leftStickMotion+leftStickTouchInputX+leftStickPhysicalInputX)
let mixedLeftStickOffsetY = self.clampStickInput(input: leftStickTouchInputY+leftStickPhysicalInputY)
let offsetVector = ControllerUtil.compensated(offsetVector: CGVector(dx: mixedLeftStickOffsetX, dy: mixedLeftStickOffsetY), minOffset: gyroToStickMinOffset)
let offsetVector = ControllerUtil.compensated(offsetVector: CGVector(dx: mixedLeftStickOffsetX, dy: mixedLeftStickOffsetY), minOffset: gyroToStickMinOffset, circulate: true)
onScreenControls?.sendLeftStickTouchPadEvent(offsetVector.dx, offsetVector.dy)
}
@@ -410,11 +413,19 @@ import CoreMotion
guard ControllerUtil.gamepadArrivalReported else { return }
if yawPitchToRightStick{
onScreenControls?.sendRightStickTouchPadEvent(rightStickPhysicalInputX+rightStickTouchInputX-yawBias, rightStickPhysicalInputY+rightStickTouchInputY-pitchBias)
let clearX = rightStickPhysicalInputX+rightStickTouchInputX-yawBias
let clearY = rightStickPhysicalInputY+rightStickTouchInputY-pitchBias
let clearVector = ControllerUtil.circulated(offsetVector: CGVector(dx: clearX, dy: clearY))
onScreenControls?.sendRightStickTouchPadEvent(clearVector.dx, clearVector.dy)
}
if rollToLeftStick{
leftStickMotion = 0
onScreenControls?.sendLeftStickTouchPadEvent(leftStickPhysicalInputX+leftStickTouchInputX-rollBias,leftStickPhysicalInputY+leftStickTouchInputY)
leftStickMotion = leftStickTouchInputX
let clearX = leftStickPhysicalInputX+leftStickTouchInputX-rollBias
let clearY = leftStickPhysicalInputY+leftStickTouchInputY
let clearVector = ControllerUtil.circulated(offsetVector: CGVector(dx: clearX, dy: clearY))
onScreenControls?.sendLeftStickTouchPadEvent(clearVector.dx,clearVector.dy)
}
if(interruptNonGyroInput){
onScreenControls?.clearLeftStickTouchPadFlag()
+8 -6
View File
@@ -2000,12 +2000,13 @@ import ObjectiveC.runtime
let mixRightStickInputToGyro = (oscProfile.mapGyroTo == .mapGyroToControllerStick
&& oscProfile.yawPitchToRightStick)
stickOffsetVector = ControllerUtil.compensated(offsetVector: CGVector(dx: targetX, dy: targetY), minOffset: minStickOffset, circulate: circulate)
if !mixRightStickInputToGyro || (self.motionHandler?.gyroMixInputStarted() != true) {
stickOffsetVector = ControllerUtil.compensated(offsetVector: CGVector(dx: targetX, dy: targetY), minOffset: minStickOffset, circulate: circulate)
self.onScreenControls?.sendRightStickTouchPadEvent(stickOffsetVector.dx, stickOffsetVector.dy)
}
self.motionHandler?.mixOnScreenRightStickAndGyroInput(x: targetX, y: targetY)
self.motionHandler?.mixOnScreenRightStickAndGyroInput(x: stickOffsetVector.dx, y: stickOffsetVector.dy)
if !ControllerUtil.gamepadArrivalReported {ControllerUtil.gamepadArrivalReported = true}
}
@@ -2015,12 +2016,13 @@ import ObjectiveC.runtime
let mixLeftStickInputToGyro = (oscProfile.mapGyroTo == .mapGyroToControllerStick
&& oscProfile.rollToLeftStick)
stickOffsetVector = ControllerUtil.compensated(offsetVector: CGVector(dx: targetX, dy: targetY), minOffset: minStickOffset, circulate: circulate)
if !mixLeftStickInputToGyro || (self.motionHandler?.gyroMixInputStarted() != true) {
stickOffsetVector = ControllerUtil.compensated(offsetVector: CGVector(dx: targetX, dy: targetY), minOffset: minStickOffset, circulate: circulate)
self.onScreenControls?.sendLeftStickTouchPadEvent(stickOffsetVector.dx, stickOffsetVector.dy)
}
self.motionHandler?.mixOnScreenLeftStickAndGyroInput(x: targetX, y: targetY)
self.motionHandler?.mixOnScreenLeftStickAndGyroInput(x: stickOffsetVector.dx, y: stickOffsetVector.dy)
if !ControllerUtil.gamepadArrivalReported {ControllerUtil.gamepadArrivalReported = true}
}
+1
View File
@@ -714,6 +714,7 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5;
OnScreenWidgetView.buttonVisualFeedbackEnabled = self->settings.buttonVisualFeedback;
OnScreenWidgetView.gamepadOverlayFLag = profile.gamepadOverlayEnabled;
ControllerUtil.dualSenseHapticTransient = profile.dualSenseTransient;
bool hasLegacyWidget = false;
if(reloadWidgets && !OnScreenWidgetView.editMode){
+13 -12
View File
@@ -8212,25 +8212,25 @@
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Sunshine emulates game controller via VigemBus, which support DS4 & Xbox360. \n\n- Xbox360 does not support gyro.\n\n- When “both” is selected, the app will map built-in gyro as player0 & external game controller as player1"
"value" : "Sunshine emulates game controller via VigemBus, which support DS4 & Xbox360. \n- Xbox360 does not support gyro.\n- When “both” is selected, the app will map built-in gyro as player0 & external game controller as player1\n\nWhen DualSense sidecar is running on Sunshine-Foundation, VigemBus will be taken over, left DualSense to be the emulated Controller on your host. In this case:\n- The option “PS (Enhanced Haptic)” provides better haptic feedback for a real DualSense controller.\n- The option “PS” provides DualSense haptic feedback translation to traditional rumbling for all controllers.\n- Enhanced haptics are also available with iPhone motor by setting “Haptic Engine” to “Device”.\n"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "Sunshine 通过 VigemBus 模拟游戏手柄,支持 DS4 和 Xbox 360。\n\n- Xbox 360 不支持陀螺仪。\n\n- 当选择“同时映射”时,app会将内置陀螺仪映射为玩家0,外接手柄映射为玩家1。"
"value" : "Sunshine 通过 VigemBus 模拟游戏手柄,支持 DS4 和 Xbox 360。\n- Xbox 360 不支持陀螺仪。\n- 当选择“同时映射”时,app会将内置陀螺仪映射为玩家0,外接手柄映射为玩家1。\n\n当 DualSense 组件在【Sunshine 基地版】上运行时,它会接管 ViGEmBus,并使 DualSense 成为主机端的模拟手柄。在这种情况下:\n- 选择“PS(触觉增强)”将为真实的DualSense 手柄提供更好的触觉反馈。\n- 选择“PS”:将 DualSense 触觉反馈转换为传统震动,适用于所有手柄。\n- 将 “振动引擎” 设置为 “内置”,也可以通过 iPhone 马达获得增强触觉反馈。\n\nDualSense的自适应板机、触觉反馈仅与Sunshine基地版配合使用时有效。"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
"value" : "Sunshine 透過 VigemBus 模擬遊戲控制器,支援 DS4 和 Xbox 360。\n\n- Xbox 360 不支援陀螺儀。\n\n- 當選擇「同時映射」時,app 會將內建陀螺儀對應為玩家0,外接控制器對應為玩家1。"
"value" : "Sunshine 透過 VigemBus 模擬遊戲控制器,支援 DS4 和 Xbox 360。\n- Xbox 360 不支援陀螺儀。\n- 當選擇「同時映射」時,app 會將內建陀螺儀對應為玩家0,外接控制器對應為玩家1。\n\n當 DualSense 元件在 「Sunshine 基地版」上執行時,它會接管 ViGEmBus,並使 DualSense 成為主機端的模擬手把。在這種情況下:\n- 選擇「PS(觸覺增強)」將為真實的 DualSense 手把提供更好的觸覺回饋。\n- 選擇「PS」:將 DualSense 觸覺回饋轉換為傳統震動,適用於所有手把。\n- 將「振動引擎」設定為「內建」,也可以透過 iPhone 馬達獲得增強觸覺回饋。\n\nDualSense 的自適應扳機、觸覺回饋僅在與 Sunshine 基地版配合使用時有效。"
}
},
"zh-HK" : {
"stringUnit" : {
"state" : "translated",
"value" : "Sunshine 透過 VigemBus 模擬遊戲控制器,支援 DS4 和 Xbox 360。\n\n- Xbox 360 不支援陀螺儀。\n\n- 當選擇「同時映射」時,app 會將內建陀螺儀對應為玩家0,外接控制器對應為玩家1。"
"value" : "Sunshine 透過 VigemBus 模擬遊戲控制器,支援 DS4 和 Xbox 360。\n- Xbox 360 不支援陀螺儀。\n- 當選擇「同時映射」時,app 會將內建陀螺儀對應為玩家0,外接控制器對應為玩家1。\n\n當 DualSense 元件在 「Sunshine 基地版」上執行時,它會接管 ViGEmBus,並使 DualSense 成為主機端的模擬手把。在這種情況下:\n- 選擇「PS(觸覺增強)」將為真實的 DualSense 手把提供更好的觸覺回饋。\n- 選擇「PS」:將 DualSense 觸覺回饋轉換為傳統震動,適用於所有手把。\n- 將「振動引擎」設定為「內建」,也可以透過 iPhone 馬達獲得增強觸覺回饋。\n\nDualSense 的自適應扳機、觸覺回饋僅在與 Sunshine 基地版配合使用時有效。"
}
}
}
@@ -8256,7 +8256,8 @@
"value" : "啟用 Metal 渲染?"
}
}
}
},
"shouldTranslate" : false
},
"enableOswForNativeTouchSwitchTip" : {
"extractionState" : "manual",
@@ -9890,25 +9891,25 @@
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "This setting is saved in [Game Profile]. To open game profile selector:\n- Tap the gamepad icon on navigation bar\n- While streaming, swipe inward from the %@ edge of the screen by %@ of screen width, open [Switch Game Profile] in the toolbox menu"
"value" : "This setting is saved in [Game Profile]. To open game profile selector:\n- Tap the gamepad icon on navigation bar\n- While streaming, swipe inward from the %@ edge of the screen by %@ of screen width, open [Switch Game Profile] in the toolbox menu\n- Also available by controller radial menu -> [more options]"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "该设置保存在 [游戏配置] 中. 游戏配置切换入口:\n- 点击导航栏的手柄图标\n- 串流时,由屏幕 %@ 边缘向内滑动 %@ 屏宽,在工具箱菜单中打开 [切换游戏配置]"
"state" : "needs_review",
"value" : "该设置保存在 [游戏配置] 中. 游戏配置切换入口:\n- 点击导航栏的手柄图标\n- 串流时,由屏幕 %@ 边缘向内滑动 %@ 屏宽,在工具箱菜单中打开 [切换游戏配置]\n- 手柄用户请在环形菜单 -> [更多] 中打开"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
"value" : "此設定保存在[遊戲配置]中。遊戲配置切換入口:\n- 點擊導覽列的手把圖示\n- 串流時,由螢幕 %@ 邊緣向內滑動 %@ 螢幕寬度,在工具箱選單中開啟[切換遊戲配置]"
"state" : "needs_review",
"value" : "此設定保存在[遊戲配置]中。遊戲配置切換入口:\n- 點擊導覽列的手把圖示\n- 串流時,由螢幕 %@ 邊緣向內滑動 %@ 螢幕寬度,在工具箱選單中開啟[切換遊戲配置]\n- 手把使用者請在環形選單 -> [更多] 中開啟"
}
},
"zh-HK" : {
"stringUnit" : {
"state" : "translated",
"value" : "此設定保存在[遊戲配置]中。遊戲配置切換入口:\n- 點擊導覽列的手把圖示\n- 串流時,由螢幕 %@ 邊緣向內滑動 %@ 螢幕寬度,在工具箱選單中開啟[切換遊戲配置]"
"state" : "needs_review",
"value" : "此設定保存在[遊戲配置]中。遊戲配置切換入口:\n- 點擊導覽列的手把圖示\n- 串流時,由螢幕 %@ 邊緣向內滑動 %@ 螢幕寬度,在工具箱選單中開啟[切換遊戲配置]\n- 手把使用者請在環形選單 -> [更多] 中開啟"
}
}
}
+5
View File
@@ -21,6 +21,7 @@
@interface Connection : NSOperation <NSStreamDelegate>
@property (class, nonatomic, assign) bool muteInBackground;
@property (class, nonatomic, assign) bool useSystemAudioEngine;
@property (class, nonatomic, assign) bool useDualSenseHapticsIrV2;
-(id) initWithConfig:(StreamConfiguration*)config renderer:(VideoDecoderRenderer*)myRenderer connectionCallbacks:(id<ConnectionCallbacks>)callbacks;
-(void) terminate;
@@ -32,4 +33,8 @@
+ (void)setVolume:(float)newVolume;
+ (void)resetSysAudioPlayback;
+ (bool)useDualSenseAuthoredPCM;
+ (void)setuseDualSenseAuthoredPCM:(bool)use;
@end
+41 -2
View File
@@ -61,6 +61,7 @@ static AVAudioFormat *audioFormat;
static bool muteInBackground;
static bool fullColorRange;
static bool request10BitCodec;
static bool useDualSenseAuthoredPCM = false;
static VideoDecoderRenderer* renderer;
@@ -357,6 +358,14 @@ void ArCleanup(void)
useSystemAudioEngine = useSysAudioEngine;
}
+ (bool)useDualSenseAuthoredPCM {
return useDualSenseAuthoredPCM;
}
+ (void)setuseDualSenseAuthoredPCM:(bool)use {
useDualSenseAuthoredPCM = use;
}
void AudioEngineInit(int sampleRate, int channelCount) {
audioEngine = [[AVAudioEngine alloc] init];
@@ -604,6 +613,30 @@ void ClDs5HapticsPcm(const LI_DS5_HAPTICS_PCM_FRAME* frame)
pcmData:pcmData];
}
void ClDs5HapticsIrV2(const LI_DS5_HAPTICS_IR_FRAME_V2* frame)
{
if (frame == NULL) {
return;
}
[ControllerUtil enqueueDualSenseHapticsIRV2WithControllerNumber:frame->controllerNumber
flags:frame->flags
sourceSequenceNumber:frame->sourceSequenceNumber
timestampUs:frame->timestampUs
sourceFrameCount:frame->sourceFrameCount
leftRms:frame->lanes[0].rmsAmplitude
leftPeak:frame->lanes[0].peakAmplitude
leftTransient:frame->lanes[0].transientStrength
leftLowRatio:frame->lanes[0].lowBandRatio
leftZeroCrossHz:frame->lanes[0].zeroCrossingRateHz
rightRms:frame->lanes[1].rmsAmplitude
rightPeak:frame->lanes[1].peakAmplitude
rightTransient:frame->lanes[1].transientStrength
rightLowRatio:frame->lanes[1].lowBandRatio
rightZeroCrossHz:frame->lanes[1].zeroCrossingRateHz
laneCorrelation:frame->laneCorrelation];
}
-(void) terminate
{
// Interrupt any action blocking LiStartConnection(). This is
@@ -706,6 +739,7 @@ void ClDs5HapticsPcm(const LI_DS5_HAPTICS_PCM_FRAME* frame)
_streamConfig.audioConfiguration = config.audioConfiguration;
_streamConfig.redirectMic = config.redirectMic && [MicHandler permissionGranted];
[Connection setVolume:config.localVolume];
// Since we require iOS 12 or above, we're guaranteed to be running
// on a 64-bit device with ARMv8 crypto instructions, so we don't
// need to check for that here.
@@ -767,8 +801,13 @@ void ClDs5HapticsPcm(const LI_DS5_HAPTICS_PCM_FRAME* frame)
_clCallbacks.setMotionEventState = ClSetMotionEventState;
_clCallbacks.setControllerLED = ClSetControllerLED;
_clCallbacks.setAdaptiveTriggers = ClSetAdaptiveTriggers;
_clCallbacks.ds5HapticsPcm = ClDs5HapticsPcm;
useDualSenseAuthoredPCM = (config.emulatedControllerType == ControllerEmulationPsEnhancedHaptic
&& ControllerUtil.hasDualSenseController) || config.hapticEngine == RumbleDevice;
if (useDualSenseAuthoredPCM) {
_clCallbacks.ds5HapticsPcm = ClDs5HapticsPcm;
// _clCallbacks.ds5HapticsIrV2 = ClDs5HapticsIrV2;
}
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleAudioSessionInterruption:)
name:AVAudioSessionInterruptionNotification
+1
View File
@@ -40,6 +40,7 @@
@property BOOL asyncNativeTouchPriority;
@property int gyroMode;
@property int emulatedControllerType;
@property int hapticEngine;
@property int audioConfiguration;
@property int supportedVideoFormats;
@property BOOL multiController;
+1 -1
View File
@@ -9,5 +9,5 @@
#import "StreamConfiguration.h"
@implementation StreamConfiguration
@synthesize host, httpsPort, appID, width, height, frameRate, bitRate, riKeyId, riKey, gamepadMask, appName, optimizeGameSettings, playAudioOnPC, swapABXYButtons, buttonVisualFeedback, gyroMode, emulatedControllerType, audioConfiguration, supportedVideoFormats, multiController, serverCert, rtspSessionUrl, serverCodecModeSupport, enableYUV444, enablePIP, fullColorRange, enableHdr, sdrPerformanceWorkaround, localVolume;
@synthesize host, httpsPort, appID, width, height, frameRate, bitRate, riKeyId, riKey, gamepadMask, appName, optimizeGameSettings, playAudioOnPC, swapABXYButtons, buttonVisualFeedback, gyroMode, emulatedControllerType, hapticEngine, audioConfiguration, supportedVideoFormats, multiController, serverCert, rtspSessionUrl, serverCodecModeSupport, enableYUV444, enablePIP, fullColorRange, enableHdr, sdrPerformanceWorkaround, localVolume;
@end
+18
View File
@@ -117,6 +117,24 @@ import UIKit
return true
}
@objc public static func handleControllerEmulationTip(in vc: UIViewController?) {
let key = "hasShownControllerEmulationTip2"
guard !UserDefaults.standard.bool(forKey: key) else {
return
}
UserDefaults.standard.set(true, forKey: key)
AlertControllerUtil.showAlert(
in: vc,
title: LocalizationHelper.localizedString(forKey: "Tips"),
message: LocalizationHelper.localizedString(forKey: "emulatedControllerTypeStackTip"),
withCancel: false,
buttonTitle: LocalizationHelper.localizedString(forKey: "Got it!"),
countdown: 7
)
}
@objc public static func needUpdateDefaultSettings() -> Bool {
// let key = "needUpdateDefaultSettings20260226-1"
@@ -882,6 +882,7 @@ static NSMutableSet* hostList;
_streamConfig.asyncNativeTouchPriority = streamSettings.asyncNativeTouchPriority; // new streamConfig segment
_streamConfig.gyroMode = [streamSettings.gyroMode intValue];
_streamConfig.emulatedControllerType = streamSettings.emulatedControllerType.intValue;
_streamConfig.hapticEngine = streamSettings.hapticEngine.intValue;
//NSLog(@"gyroMode from settings: %ld", _streamConfig.gyroMode);
// multiController must be set before calling getConnectedGamepadMask
@@ -268,9 +268,6 @@
@property (strong, nonatomic) IBOutlet UIStackView *pressureCurveStack;
@property (strong, nonatomic) IBOutlet UISwitch *pressureCurveSwitch;
@property (weak, nonatomic) IBOutlet UIStackView *frameTimebaseStack;
@property (weak, nonatomic) IBOutlet UISwitch *frameTimebaseSwitch;
@property (weak, nonatomic) IBOutlet UIStackView *asyncFrameDequeueStack;
@property (weak, nonatomic) IBOutlet UISwitch *asyncFrameDequeueSwitch;
@@ -283,6 +280,9 @@
@property (weak, nonatomic) IBOutlet UIStackView *globeAsEscapeStack;
@property (weak, nonatomic) IBOutlet UISwitch *globeAsEscapeSwitch;
@property (weak, nonatomic) IBOutlet UIStackView *dualSenseTransientStack;
@property (weak, nonatomic) IBOutlet UISlider *dualSenseTransientSlider;
@property (strong, nonatomic) IBOutlet UIStackView *testStack;
@property (nonatomic, strong) MicHandler *micHandler;
@@ -211,15 +211,21 @@ CMVideoDimensions resolutionTable[RESOLUTION_TABLE_SIZE];
-(uint16_t)controllerTypeToSegmentIndex:(uint16_t)type{
uint16_t index;
switch (type) {
case LI_CTYPE_XBOX:
case ControllerEmulationXbox:
index = 0;
break;
case LI_CTYPE_PS:
case ControllerEmulationPs:
index = 1;
break;
default:
case ControllerEmulationPsEnhancedHaptic:
index = 2;
break;
case ControllerEmulationXboxAndPs:
index = 3;
break;
default:
index = 3;
break;
}
return index;
}
@@ -228,10 +234,16 @@ CMVideoDimensions resolutionTable[RESOLUTION_TABLE_SIZE];
uint16_t type;
switch (index) {
case 0:
type = LI_CTYPE_XBOX;
type = ControllerEmulationXbox;
break;
case 1:
type = LI_CTYPE_PS;
type = ControllerEmulationPs;
break;
case 2:
type = ControllerEmulationPsEnhancedHaptic;
break;
case 3:
type = ControllerEmulationXboxAndPs;
break;
default:
type = LI_CTYPE_UNKNOWN;
@@ -428,6 +440,9 @@ BOOL isCustomResolution(int resolutionSelected) {
[self.synthPhysicalInputSwitch setOn:oscProfile.synthesizePhysicalStick];
[self.dualSenseTransientSlider setValue:oscProfile.dualSenseTransient];
[self.dualSenseTransientSlider sendActionsForControlEvents:UIControlEventValueChanged];
[self.pressureCurveSwitch setOn:oscProfile.pressureCurveEnabled];
[self.doubleTapShortcutSwitch setOn:oscProfile.doubleTapShorcutEnabled];
[self.squeezeShortcutSwitch setOn:oscProfile.squeezeShorcutEnabled];
@@ -461,6 +476,7 @@ BOOL isCustomResolution(int resolutionSelected) {
&& (int16_t)(oscProfile.gyroSensitivityPitch*100) == (int16_t)pitchSensitivityPercent
&& (int16_t)(oscProfile.gyroSensitivityRoll*100) == (int16_t)rollSensitivityPercent
&& (int16_t)(oscProfile.gyroToStickMinOffset) == (int16_t)self.gyroToStickMinOffsetSlider.value
&& (int16_t)(oscProfile.dualSenseTransient*100) == (int16_t)(self.dualSenseTransientSlider.value*100)
&& oscProfile.synthesizePhysicalStick == self.synthPhysicalInputSwitch.isOn
&& oscProfile.controllerGyroSwitchMode == self.controllerGyroSwitchButtonSetter.selectedSegmentIndex
&& oscProfile.reverseGyroHoldButton == self.reverseHoldButtonSwitch.isOn
@@ -488,6 +504,7 @@ BOOL isCustomResolution(int resolutionSelected) {
oscProfile.gyroSensitivityPitch = pitchSensitivityPercent/100;
oscProfile.gyroSensitivityRoll = rollSensitivityPercent/100;
oscProfile.gyroToStickMinOffset = (int16_t)self.gyroToStickMinOffsetSlider.value;
oscProfile.dualSenseTransient = self.dualSenseTransientSlider.value;
oscProfile.synthesizePhysicalStick = self.synthPhysicalInputSwitch.isOn;
oscProfile.controllerGyroSwitchMode = (int)self.controllerGyroSwitchButtonSetter.selectedSegmentIndex;
oscProfile.reverseGyroHoldButton = self.reverseHoldButtonSwitch.isOn;
@@ -996,6 +1013,11 @@ BOOL isCustomResolution(int resolutionSelected) {
self.emulatedControllerTypeStack.hasInfoTag = YES;
// self.emulatedControllerTypeStack.isGameProfileSetting = YES;
[self addSetting:self.emulatedControllerTypeStack ofId:@"emulatedControllerTypeStack" to:controllerSection];
self.dualSenseTransientStack.hasInfoTag = YES;
self.dualSenseTransientStack.isGameProfileSetting = YES;
self.dualSenseTransientStack.hasDynamicLabel = YES;
[self addSetting:self.dualSenseTransientStack ofId:@"dualSenseTransientStack" to:controllerSection];
self.gyroModeStack.hasInfoTag = YES;
// self.gyroModeStack.isGameProfileSetting = YES;
@@ -2321,6 +2343,8 @@ BOOL isCustomResolution(int resolutionSelected) {
[self.hapticEngineSelector setEnabled:!disableControllerRumble forSegmentAtIndex:LeftRightSwapped];
[self.hapticEngineSelector setEnabled:!disableControllerRumble forSegmentAtIndex:HapticEngineAuto];
[self.hapticEngineSelector setEnabled:[self isIPhone] forSegmentAtIndex:RumbleDevice];
[self.dualSenseTransientSlider addTarget:self action:@selector(dualSenseTransientSliderMoved:) forControlEvents:UIControlEventValueChanged];
self.gyroModeSelector.selectedSegmentIndex = self->tempSettings.gyroMode.intValue;
[self.gyroSensitivitySlider setValue: (uint16_t)(self->tempSettings.gyroSensitivity.floatValue * 100) animated:NO]; // Load old setting.
@@ -3425,6 +3449,11 @@ BOOL isCustomResolution(int resolutionSelected) {
}
}
- (void)dualSenseTransientSliderMoved:(UISlider* )sender {
[self findDynamicLabelFromStack:(UIStackView*)sender.superview].text = [NSString stringWithFormat:@" %.2f ", sender.value]; // Update label display
ControllerUtil.dualSenseHapticTransient = sender.value;
}
- (void) gyroSensitivitySliderMoved:(UISlider* )sender {
[self findDynamicLabelFromStack:(UIStackView*)sender.superview].text = [NSString stringWithFormat:@" %d%% ", (uint16_t)sender.value]; // Update label display
}
@@ -3619,6 +3648,12 @@ BOOL isCustomResolution(int resolutionSelected) {
- (void)emulatedControllerTypeChanged:(UISegmentedControl* )sender{
[self setHidden:sender.selectedSegmentIndex == 0 forStack:_gyroModeStack];
[self setHidden:sender.selectedSegmentIndex == 0 forStack:_gyroSensitivityStack];
[self setHidden:sender.selectedSegmentIndex != 2 forStack:_dualSenseTransientStack];
if(settingsViewJustExpanded) return;
dispatch_async(dispatch_get_main_queue(), ^{
[GenericUtils handleControllerEmulationTipIn:self];
});
}
+49 -19
View File
@@ -2425,18 +2425,6 @@
}
}
},
"cpg-mq-kYj.text" : {
"comment" : "Class = \"UILabel\"; text = \"Enable Timebase\"; ObjectID = \"cpg-mq-kYj\";",
"extractionState" : "extracted_with_value",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "Enable Timebase"
}
}
}
},
"CpY-Sb-BDc.title" : {
"comment" : "Class = \"UIBarButtonItem\"; title = \"Import\"; ObjectID = \"CpY-Sb-BDc\";",
"extractionState" : "stale",
@@ -3999,43 +3987,55 @@
}
},
"JaY-XA-1V6.segmentTitles[1]" : {
"comment" : "Class = \"UISegmentedControl\"; JaY-XA-1V6.segmentTitles[1] = \"DS4\"; ObjectID = \"JaY-XA-1V6\";",
"comment" : "Class = \"UISegmentedControl\"; JaY-XA-1V6.segmentTitles[1] = \"PS\"; ObjectID = \"JaY-XA-1V6\";",
"extractionState" : "extracted_with_value",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "DS4"
"value" : "PS"
}
}
}
},
"JaY-XA-1V6.segmentTitles[2]" : {
"comment" : "Class = \"UISegmentedControl\"; JaY-XA-1V6.segmentTitles[2] = \"Both\"; ObjectID = \"JaY-XA-1V6\";",
"comment" : "Class = \"UISegmentedControl\"; JaY-XA-1V6.segmentTitles[2] = \"PS (Enhanced Haptics)\"; ObjectID = \"JaY-XA-1V6\";",
"extractionState" : "extracted_with_value",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "Both"
"value" : "PS (Enhanced Haptics)"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "同时映射"
"value" : "PS (触觉增强)"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
"value" : "同時映射"
"value" : "PS(觸覺增強)"
}
},
"zh-HK" : {
"stringUnit" : {
"state" : "translated",
"value" : "同時映射"
"value" : "PS(觸覺增強)"
}
}
}
},
"JaY-XA-1V6.segmentTitles[3]" : {
"comment" : "Class = \"UISegmentedControl\"; JaY-XA-1V6.segmentTitles[3] = \"Xbox + PS\"; ObjectID = \"JaY-XA-1V6\";",
"extractionState" : "extracted_with_value",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "Xbox + PS"
}
}
}
@@ -6598,6 +6598,36 @@
}
}
},
"UpM-yi-yc9.text" : {
"comment" : "Class = \"UILabel\"; text = \"DualSense Haptic Intensity\"; ObjectID = \"UpM-yi-yc9\";",
"extractionState" : "extracted_with_value",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "DualSense Haptic Intensity"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "DualSense振动强度"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
"value" : "DualSense振動強度"
}
},
"zh-HK" : {
"stringUnit" : {
"state" : "translated",
"value" : "DualSense振動強度"
}
}
}
},
"V1y-Wk-uHM.text" : {
"comment" : "Class = \"UILabel\"; text = \"Height\"; ObjectID = \"V1y-Wk-uHM\";",
"extractionState" : "extracted_with_value",
+49 -19
View File
@@ -4789,18 +4789,6 @@
}
}
},
"P4N-i6-1zD.text" : {
"comment" : "Class = \"UILabel\"; text = \"Enable Timebase\"; ObjectID = \"P4N-i6-1zD\";",
"extractionState" : "extracted_with_value",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "Enable Timebase"
}
}
}
},
"P9b-Th-Ybc.text" : {
"comment" : "Class = \"UILabel\"; text = \" Batch move \"; ObjectID = \"P9b-Th-Ybc\";",
"extractionState" : "extracted_with_value",
@@ -5216,43 +5204,55 @@
}
},
"q6v-Jb-ysy.segmentTitles[1]" : {
"comment" : "Class = \"UISegmentedControl\"; q6v-Jb-ysy.segmentTitles[1] = \"DS4\"; ObjectID = \"q6v-Jb-ysy\";",
"comment" : "Class = \"UISegmentedControl\"; q6v-Jb-ysy.segmentTitles[1] = \"PS\"; ObjectID = \"q6v-Jb-ysy\";",
"extractionState" : "extracted_with_value",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "DS4"
"value" : "PS"
}
}
}
},
"q6v-Jb-ysy.segmentTitles[2]" : {
"comment" : "Class = \"UISegmentedControl\"; q6v-Jb-ysy.segmentTitles[2] = \"Both\"; ObjectID = \"q6v-Jb-ysy\";",
"comment" : "Class = \"UISegmentedControl\"; q6v-Jb-ysy.segmentTitles[2] = \"PS (Enhanced Haptics)\"; ObjectID = \"q6v-Jb-ysy\";",
"extractionState" : "extracted_with_value",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "Both"
"value" : "PS (Enhanced Haptics)"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "同时映射"
"value" : "PS (触觉增强)"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
"value" : "同時映射"
"value" : "PS(觸覺增強)"
}
},
"zh-HK" : {
"stringUnit" : {
"state" : "translated",
"value" : "同時映射"
"value" : "PS(觸覺增強)"
}
}
}
},
"q6v-Jb-ysy.segmentTitles[3]" : {
"comment" : "Class = \"UISegmentedControl\"; q6v-Jb-ysy.segmentTitles[3] = \"Xbox + PS\"; ObjectID = \"q6v-Jb-ysy\";",
"extractionState" : "extracted_with_value",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "Xbox + PS"
}
}
}
@@ -6325,6 +6325,36 @@
}
}
},
"vak-fg-iAQ.text" : {
"comment" : "Class = \"UILabel\"; text = \"DualSense Haptic Intensity\"; ObjectID = \"vak-fg-iAQ\";",
"extractionState" : "extracted_with_value",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "DualSense Haptic Intensity"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "DualSense振动强度"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
"value" : "DualSense振動強度"
}
},
"zh-HK" : {
"stringUnit" : {
"state" : "translated",
"value" : "DualSense振動強度"
}
}
}
},
"VEc-6n-QcS.text" : {
"comment" : "Class = \"UILabel\"; text = \"Lift Stream View for Local Keyboard \"; ObjectID = \"VEc-6n-QcS\";",
"extractionState" : "extracted_with_value",