From dd0c36bb20bdef9dd978f50d6643d6b4fbc5203c Mon Sep 17 00:00:00 2001 From: TrueZhuanjia Date: Sun, 12 Oct 2025 16:49:10 +0800 Subject: [PATCH] feat: haptic engine selector --- VoidLink/Base.lproj/iPad.storyboard | 42 ++++- VoidLink/Base.lproj/iPhone.storyboard | 34 +++- VoidLink/Database/DataManager.h | 8 + VoidLink/Database/DataManager.m | 2 + VoidLink/Database/TemporarySettings.h | 1 + VoidLink/Database/TemporarySettings.m | 1 + VoidLink/Input/ControllerSupport.m | 41 ++++- VoidLink/Input/HapticContext.m | 84 +++++++--- .../VoidLink v1.0.xcdatamodel/contents | 1 + VoidLink/MenuSectionView.m | 2 +- .../ViewControllers/SettingsViewController.h | 3 + .../ViewControllers/SettingsViewController.m | 19 +++ VoidLink/mul.lproj/iPad.xcstrings | 150 ++++++++++++++++++ VoidLink/mul.lproj/iPhone.xcstrings | 150 ++++++++++++++++++ 14 files changed, 501 insertions(+), 37 deletions(-) diff --git a/VoidLink/Base.lproj/iPad.storyboard b/VoidLink/Base.lproj/iPad.storyboard index ac316243..4b678969 100644 --- a/VoidLink/Base.lproj/iPad.storyboard +++ b/VoidLink/Base.lproj/iPad.storyboard @@ -1360,16 +1360,16 @@ - + - + @@ -1379,6 +1379,32 @@ + + + + + + + + + + + + + + + + + + + + + @@ -1422,6 +1448,8 @@ + + @@ -2229,7 +2257,7 @@ - + @@ -2238,13 +2266,13 @@ - + - + - + diff --git a/VoidLink/Base.lproj/iPhone.storyboard b/VoidLink/Base.lproj/iPhone.storyboard index aa5aa306..e8ad5e9c 100644 --- a/VoidLink/Base.lproj/iPhone.storyboard +++ b/VoidLink/Base.lproj/iPhone.storyboard @@ -1704,16 +1704,16 @@ - + - + @@ -2111,6 +2111,32 @@ + + + + + + + + + + + + + + + + + + + + + @@ -2155,6 +2181,8 @@ + + diff --git a/VoidLink/Database/DataManager.h b/VoidLink/Database/DataManager.h index d0ee1291..ca1c5bb8 100644 --- a/VoidLink/Database/DataManager.h +++ b/VoidLink/Database/DataManager.h @@ -43,6 +43,13 @@ typedef NS_ENUM(NSInteger, GyroMode) { AlwaysController }; +typedef NS_ENUM(NSInteger, HapticEnginePreference) { + HapticEngineAuto, + RumbleDevice, + LeftRightSwapped, + RumbleOff +}; + typedef NS_ENUM(NSInteger, FramePacingMode) { FramePacingModeOff, FramePacingModeLegacy, @@ -111,6 +118,7 @@ typedef NS_ENUM(NSInteger, WidgetSizeTransition) { sendDummyEvent:(BOOL)sendDummyEvent rememberFoldState:(BOOL)rememberFoldState singleTapSensitivy:(CGFloat)singleTapSensitivy + hapticEngine:(NSInteger)hapticEngine backgroundSessionTimer:(NSInteger)backgroundSessionTimer; - (NSArray*) getHosts; diff --git a/VoidLink/Database/DataManager.m b/VoidLink/Database/DataManager.m index 461a1ab7..2e58d9d7 100644 --- a/VoidLink/Database/DataManager.m +++ b/VoidLink/Database/DataManager.m @@ -106,6 +106,7 @@ sendDummyEvent:(BOOL)sendDummyEvent rememberFoldState:(BOOL)rememberFoldState singleTapSensitivy:(CGFloat)singleTapSensitivy + hapticEngine:(NSInteger)hapticEngine backgroundSessionTimer:(NSInteger)backgroundSessionTimer{ [_managedObjectContext performBlockAndWait:^{ @@ -162,6 +163,7 @@ settingsToSave.framePacingMode = [NSNumber numberWithInteger:framePacingMode]; settingsToSave.sendDummyEvent = sendDummyEvent; settingsToSave.singleTapSensitivity = [NSNumber numberWithDouble:singleTapSensitivy]; + settingsToSave.hapticEngine = [NSNumber numberWithInteger:hapticEngine]; settingsToSave.rememberFoldState = rememberFoldState; [self saveData]; }]; diff --git a/VoidLink/Database/TemporarySettings.h b/VoidLink/Database/TemporarySettings.h index 7fedcfc7..754c8c2d 100644 --- a/VoidLink/Database/TemporarySettings.h +++ b/VoidLink/Database/TemporarySettings.h @@ -79,6 +79,7 @@ @property (nonatomic) BOOL sendDummyEvent; @property (nonatomic) BOOL rememberFoldState; @property (nonatomic, retain) NSNumber * singleTapSensitivity; +@property (nonatomic, retain) NSNumber * hapticEngine; @property (nonatomic, retain) NSNumber * framePacingMode; - (id) initFromSettings:(Settings*)settings; diff --git a/VoidLink/Database/TemporarySettings.m b/VoidLink/Database/TemporarySettings.m index a0218eb7..856a9298 100644 --- a/VoidLink/Database/TemporarySettings.m +++ b/VoidLink/Database/TemporarySettings.m @@ -135,6 +135,7 @@ self.gyroBiasZ = settings.gyroBiasZ; self.singleTapSensitivity = settings.singleTapSensitivity; self.backgroundSessionTimer = settings.backroundSessionTimer; + self.hapticEngine = settings.hapticEngine; #endif self.uniqueId = settings.uniqueId; diff --git a/VoidLink/Input/ControllerSupport.m b/VoidLink/Input/ControllerSupport.m index 939a5c22..c060332b 100644 --- a/VoidLink/Input/ControllerSupport.m +++ b/VoidLink/Input/ControllerSupport.m @@ -53,6 +53,7 @@ static const double MOUSE_SPEED_DIVISOR = 1.25; OnScreenControls *_osc; VoidController *_oscController; NSMutableSet* _activeGCControllers; + TemporarySettings* tempSettings; #define EMULATING_SELECT 0x1 #define EMULATING_SPECIAL 0x2 @@ -75,16 +76,40 @@ static const double MOUSE_SPEED_DIVISOR = 1.25; -(void) rumble:(unsigned short)controllerNumber lowFreqMotor:(unsigned short)lowFreqMotor highFreqMotor:(unsigned short)highFreqMotor { VoidController* voidController = [_voidControllers objectForKey:[NSNumber numberWithInteger:controllerNumber]]; + + HapticEnginePreference preference = tempSettings.hapticEngine.intValue; + if (voidController == nil && controllerNumber == 0 && _oscEnabled) { - // TODO: Rumble emulation for OSC + // no physical controller connected: + if(preference != RumbleOff){ + [_oscController.lowFreqMotor setMotorAmplitude:lowFreqMotor]; + [_oscController.highFreqMotor setMotorAmplitude:highFreqMotor]; + } } + if (voidController == nil) { // No connected controller for this player return; } - [voidController.lowFreqMotor setMotorAmplitude:lowFreqMotor]; - [voidController.highFreqMotor setMotorAmplitude:highFreqMotor]; + // physical controller connected: + switch (preference) { + case HapticEngineAuto: + // if controller has no haptic profile, it already falled bakc to device engine + [voidController.lowFreqMotor setMotorAmplitude:lowFreqMotor]; + [voidController.highFreqMotor setMotorAmplitude:highFreqMotor]; + break; + case RumbleDevice: + [_oscController.lowFreqMotor setMotorAmplitude:lowFreqMotor]; + [_oscController.highFreqMotor setMotorAmplitude:highFreqMotor]; + case LeftRightSwapped: + [voidController.lowFreqMotor setMotorAmplitude:highFreqMotor]; + [voidController.highFreqMotor setMotorAmplitude:lowFreqMotor]; + case RumbleOff: + break; + default: + break; + } } -(void) rumbleTriggers:(uint16_t)controllerNumber leftTrigger:(uint16_t)leftTrigger rightTrigger:(uint16_t)rightTrigger @@ -1387,9 +1412,10 @@ static const double MOUSE_SPEED_DIVISOR = 1.25; _oscController.playerIndex = 0; DataManager* dataMan = [[DataManager alloc] init]; - TemporarySettings* currentSettings = [dataMan getSettings]; - _oscEnabled = _oscEnabled || (OnScreenControlsLevel)[currentSettings.onscreenControls integerValue] != OnScreenControlsLevelOff || streamConfig.gyroMode != GyroModeOff; - _gyroSensitivity = currentSettings.gyroSensitivity.floatValue; + tempSettings = [dataMan getSettings]; + + _oscEnabled = _oscEnabled || (OnScreenControlsLevel)[tempSettings.onscreenControls integerValue] != OnScreenControlsLevelOff || streamConfig.gyroMode != GyroModeOff; + _gyroSensitivity = tempSettings.gyroSensitivity.floatValue; } - (void)resetGyroInputForController:(VoidController* )voidController{ @@ -1435,7 +1461,7 @@ static const double MOUSE_SPEED_DIVISOR = 1.25; self = [super init]; NSLog(@"controller support init"); - + _delegate = delegate; _controllerStreamLock = [[NSLock alloc] init]; _voidControllers = [[NSMutableDictionary alloc] init]; @@ -1579,6 +1605,7 @@ static const double MOUSE_SPEED_DIVISOR = 1.25; _oscController = [[VoidController alloc] init]; + [self initializeControllerHaptics:_oscController]; _gyroMode = AlwaysDevice; [self updateCommonConfig:streamConfig]; diff --git a/VoidLink/Input/HapticContext.m b/VoidLink/Input/HapticContext.m index 07749fe9..a52d1c7a 100644 --- a/VoidLink/Input/HapticContext.m +++ b/VoidLink/Input/HapticContext.m @@ -7,6 +7,7 @@ // #import "HapticContext.h" +#import "DataManager.h" @import CoreHaptics; @import GameController; @@ -83,29 +84,66 @@ } } +-(id) initDeviceEngineContextWithGamepad:(GCController*)gamepad API_AVAILABLE(ios(13.0), tvos(13.0)) { + NSError *error = nil; + _hapticEngine = [[CHHapticEngine alloc] initAndReturnError:&error]; + if (error != nil) { + Log(LOG_W, @"Controller %d: iPhone Haptic engine failed to start: %@", gamepad.playerIndex, error); + return nil; + } + _playerIndex = gamepad.playerIndex; + + [_hapticEngine startAndReturnError:&error]; + if (error != nil) { + Log(LOG_W, @"Controller %d: Haptic engine failed to start: %@", gamepad.playerIndex, error); + return nil; + } + + __weak typeof(self) weakSelf = self; + _hapticEngine.stoppedHandler = ^(CHHapticEngineStoppedReason stoppedReason) { + HapticContext* me = weakSelf; + if (me == nil) { + return; + } + + Log(LOG_W, @"Controller %d: Haptic engine stopped: %p", me->_playerIndex, stoppedReason); + me->_hapticPlayer = nil; + me->_hapticEngine = nil; + me->_playing = NO; + }; + _hapticEngine.resetHandler = ^{ + HapticContext* me = weakSelf; + if (me == nil) { + return; + } + + Log(LOG_W, @"Controller %d: Haptic engine reset", me->_playerIndex); + me->_hapticPlayer = nil; + me->_playing = NO; + [me->_hapticEngine startAndReturnError:nil]; + }; + + return self; +} + -(id) initWithGamepad:(GCController*)gamepad locality:(GCHapticsLocality)locality API_AVAILABLE(ios(14.0), tvos(14.0)) { - bool needFallBackToPhoneHaptics = false; + bool fallBackToPhoneHaptics = false; if (gamepad.haptics == nil) { Log(LOG_W, @"Controller %d does not support haptics", gamepad.playerIndex); - needFallBackToPhoneHaptics = true; + fallBackToPhoneHaptics = true; } else if (![[gamepad.haptics supportedLocalities] containsObject:locality]) { Log(LOG_W, @"Controller %d does not support haptic locality: %@", gamepad.playerIndex, locality); - needFallBackToPhoneHaptics = true; + fallBackToPhoneHaptics = true; } - - if (needFallBackToPhoneHaptics) { - if (@available(iOS 13.0, *)) { - Log(LOG_W, @"Controller %d falls back to use iPhone Haptics for locality: %@", gamepad.playerIndex, locality); - NSError *error = nil; - _hapticEngine = [[CHHapticEngine alloc] initAndReturnError:&error]; - if (error != nil) { - Log(LOG_W, @"Controller %d: iPhone Haptic engine failed to start: %@", gamepad.playerIndex, error); - return nil; - } - } - else { + + if (fallBackToPhoneHaptics) { + Log(LOG_W, @"Controller %d falls back to use iPhone Haptics for locality: %@", gamepad.playerIndex, locality); + NSError *error = nil; + _hapticEngine = [[CHHapticEngine alloc] initAndReturnError:&error]; + if (error != nil) { + Log(LOG_W, @"Controller %d: iPhone Haptic engine failed to start: %@", gamepad.playerIndex, error); return nil; } } @@ -154,7 +192,9 @@ return [[HapticContext alloc] initWithGamepad:gamepad locality:GCHapticsLocalityRightHandle]; } else { - return nil; + if (@available(iOS 13.0, *)) { + return [[HapticContext alloc] initDeviceEngineContextWithGamepad:gamepad]; + } else return nil; } } @@ -163,7 +203,9 @@ return [[HapticContext alloc] initWithGamepad:gamepad locality:GCHapticsLocalityLeftHandle]; } else { - return nil; + if (@available(iOS 13.0, *)) { + return [[HapticContext alloc] initDeviceEngineContextWithGamepad:gamepad]; + } else return nil; } } @@ -172,7 +214,9 @@ return [[HapticContext alloc] initWithGamepad:gamepad locality:GCHapticsLocalityLeftTrigger]; } else { - return nil; + if (@available(iOS 13.0, *)) { + return [[HapticContext alloc] initDeviceEngineContextWithGamepad:gamepad]; + } else return nil; } } @@ -181,7 +225,9 @@ return [[HapticContext alloc] initWithGamepad:gamepad locality:GCHapticsLocalityRightTrigger]; } else { - return nil; + if (@available(iOS 13.0, *)) { + return [[HapticContext alloc] initDeviceEngineContextWithGamepad:gamepad]; + } else return nil; } } diff --git a/VoidLink/Limelight.xcdatamodeld/VoidLink v1.0.xcdatamodel/contents b/VoidLink/Limelight.xcdatamodeld/VoidLink v1.0.xcdatamodel/contents index 1d9dce47..89cee3aa 100644 --- a/VoidLink/Limelight.xcdatamodeld/VoidLink v1.0.xcdatamodel/contents +++ b/VoidLink/Limelight.xcdatamodeld/VoidLink v1.0.xcdatamodel/contents @@ -43,6 +43,7 @@ + diff --git a/VoidLink/MenuSectionView.m b/VoidLink/MenuSectionView.m index 833d97fb..21f83650 100644 --- a/VoidLink/MenuSectionView.m +++ b/VoidLink/MenuSectionView.m @@ -324,8 +324,8 @@ static BOOL overridePersistedFoldState = YES; - (void)toggleFold { self.isExpanded = !_expandable ? false : !self.isExpanded; - [self updateViewForFoldState]; if(!_expandable) self.lockedSectionHandler(); + [self updateViewForFoldState]; [[NSUserDefaults standardUserDefaults] setBool:self.isExpanded forKey:self.identifier]; [[NSUserDefaults standardUserDefaults] synchronize]; } diff --git a/VoidLink/ViewControllers/SettingsViewController.h b/VoidLink/ViewControllers/SettingsViewController.h index 595e1014..667dff25 100644 --- a/VoidLink/ViewControllers/SettingsViewController.h +++ b/VoidLink/ViewControllers/SettingsViewController.h @@ -169,6 +169,9 @@ @property (strong, nonatomic) IBOutlet UIStackView *singleTapSensitivityStack; @property (strong, nonatomic) IBOutlet UISlider *singleTapSensitivitySlider; +@property (strong, nonatomic) IBOutlet UIStackView *hapticEngineStack; +@property (strong, nonatomic) IBOutlet UISegmentedControl *hapticEngineSelector; + @property (strong, nonatomic) IBOutlet UIStackView *testStack; @property (nonatomic, strong) MicHandler *micHandler; diff --git a/VoidLink/ViewControllers/SettingsViewController.m b/VoidLink/ViewControllers/SettingsViewController.m index ce939afb..2cc3e203 100644 --- a/VoidLink/ViewControllers/SettingsViewController.m +++ b/VoidLink/ViewControllers/SettingsViewController.m @@ -700,6 +700,7 @@ BOOL isCustomResolution(int resolutionSelected) { [self addSetting:self.onScreenWidgetStack ofId:@"onScreenWidgetStack" withInfoTag:YES withDynamicLabel:YES to:touchAndControlSection]; [self addSetting:self.buttonVisualFeedbackStack ofId:@"buttonVisualFeedbackStack" withInfoTag:NO withDynamicLabel:NO to:touchAndControlSection]; [self addSetting:self.swapAbxyStack ofId:@"swapAbaxyStack" withInfoTag:NO withDynamicLabel:NO to:touchAndControlSection]; + [self addSetting:self.hapticEngineStack ofId:@"hapticEngineStack" withInfoTag:NO withDynamicLabel:NO to:touchAndControlSection]; [self addSetting:self.emulatedControllerTypeStack ofId:@"emulatedControllerTypeStack" withInfoTag:YES withDynamicLabel:NO to:touchAndControlSection]; [self addSetting:self.gyroModeStack ofId:@"gyroModeStack" withInfoTag:YES withDynamicLabel:YES to:touchAndControlSection]; [self addSetting:self.gyroSensitivityStack ofId:@"gyroSensitivityStack" withInfoTag:NO withDynamicLabel:YES to:touchAndControlSection]; @@ -1648,6 +1649,18 @@ BOOL isCustomResolution(int resolutionSelected) { [self.multiControllerSwitch setOn:self->tempSettings.multiController]; [self.swapAbxySwitch setOn:self->tempSettings.swapABXYButtons]; [self.buttonVisualFeedbackSwitch setOn:self->tempSettings.buttonVisualFeedback]; + + [self.hapticEngineSelector setSelectedSegmentIndex:self->tempSettings.hapticEngine.intValue]; + bool hideHapticEngineStack = false; + if(@available(iOS 13.0, tvOS 13.0, *)) hideHapticEngineStack = false; + else hideHapticEngineStack = true; + [self setHidden:hideHapticEngineStack forStack:self.hapticEngineStack]; + bool disableControllerRumble = false; + if(@available(iOS 14.0, tvOS 14.0, *)) nil; + else disableControllerRumble = true; + [self.hapticEngineSelector setEnabled:!disableControllerRumble forSegmentAtIndex:LeftRightSwapped]; + [self.hapticEngineSelector setEnabled:!disableControllerRumble forSegmentAtIndex:HapticEngineAuto]; + [self.hapticEngineSelector setEnabled:[self isIPhone] forSegmentAtIndex:RumbleDevice]; [self.gyroModeSelector setSelectedSegmentIndex:self->tempSettings.gyroMode.intValue]; [self.gyroSensitivitySlider setValue: (uint16_t)(self->tempSettings.gyroSensitivity.floatValue * 100) animated:NO]; // Load old setting. @@ -1838,6 +1851,10 @@ BOOL isCustomResolution(int resolutionSelected) { completion:^{ if(!CountdownAlertController.actionCancelled){ [weakSelf.onScreenWidgetSelector setSelectedSegmentIndex:OnScreenControlsLevelCustom]; + if(weakSelf.touchModeSelector.selectedSegmentIndex == NativeTouch){ + [weakSelf.enableOswForNativeTouchSwitch setOn:true]; + [weakSelf enableOswForNativeTouchSwitchFlipped:weakSelf.enableOswForNativeTouchSwitch]; + } __strong typeof(weakSelf) strongSelf = weakSelf; if (!strongSelf) return; strongSelf->motionControlSection.expandable = true; @@ -2792,6 +2809,7 @@ BOOL isCustomResolution(int resolutionSelected) { BOOL sendDummyEvent = self.sendDummyEventSwitch.isOn; BOOL rememberFoldState = self.rememberFoldStateSwitch.isOn; CGFloat singleTapSensitivity = self.singleTapSensitivitySlider.value; + NSInteger hapticEngine = self.hapticEngineSelector.selectedSegmentIndex; NSInteger backgroundSessionTimer = self.backgroundSessionTimerSlider.value == self.backgroundSessionTimerSlider.maximumValue ? (uint32_t) INT16_MAX : (uint32_t)self.backgroundSessionTimerSlider.value; [dataMan saveSettingsWithBitrate:_bitrate @@ -2844,6 +2862,7 @@ BOOL isCustomResolution(int resolutionSelected) { sendDummyEvent:sendDummyEvent rememberFoldState:rememberFoldState singleTapSensitivy:singleTapSensitivity + hapticEngine:hapticEngine backgroundSessionTimer:backgroundSessionTimer]; } diff --git a/VoidLink/mul.lproj/iPad.xcstrings b/VoidLink/mul.lproj/iPad.xcstrings index 5f7bc9e1..36ec5115 100644 --- a/VoidLink/mul.lproj/iPad.xcstrings +++ b/VoidLink/mul.lproj/iPad.xcstrings @@ -3145,6 +3145,126 @@ } } }, + "MzN-ZR-qyM.segmentTitles[0]" : { + "comment" : "Class = \"UISegmentedControl\"; MzN-ZR-qyM.segmentTitles[0] = \"Auto\"; ObjectID = \"MzN-ZR-qyM\";", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Auto" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "自动" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "自動" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "自動" + } + } + } + }, + "MzN-ZR-qyM.segmentTitles[1]" : { + "comment" : "Class = \"UISegmentedControl\"; MzN-ZR-qyM.segmentTitles[1] = \"Built-in\"; ObjectID = \"MzN-ZR-qyM\";", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Built-in" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "内置" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "内建" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "内建" + } + } + } + }, + "MzN-ZR-qyM.segmentTitles[2]" : { + "comment" : "Class = \"UISegmentedControl\"; MzN-ZR-qyM.segmentTitles[2] = \"L/R Swapped\"; ObjectID = \"MzN-ZR-qyM\";", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "L/R Swapped" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "左右交换" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "左右交換" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "左右交換" + } + } + } + }, + "MzN-ZR-qyM.segmentTitles[3]" : { + "comment" : "Class = \"UISegmentedControl\"; MzN-ZR-qyM.segmentTitles[3] = \"Disabled\"; ObjectID = \"MzN-ZR-qyM\";", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Disabled" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "关闭" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "關閉" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "關閉" + } + } + } + }, "nh6-cn-z23.text" : { "comment" : "Class = \"UILabel\"; text = \"Toggle Local Soft Keyboard\"; ObjectID = \"nh6-cn-z23\";", "extractionState" : "extracted_with_value", @@ -4201,6 +4321,36 @@ } } }, + "YM8-us-6sh.text" : { + "comment" : "Class = \"UILabel\"; text = \"Haptic Engine\"; ObjectID = \"YM8-us-6sh\";", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Haptic Engine" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "振动引擎" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "振動引擎" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "振動引擎" + } + } + } + }, "yY7-Sf-Zes.segmentTitles[0]" : { "comment" : "Class = \"UISegmentedControl\"; yY7-Sf-Zes.segmentTitles[0] = \"Slide From Left Edge\"; ObjectID = \"yY7-Sf-Zes\";", "extractionState" : "extracted_with_value", diff --git a/VoidLink/mul.lproj/iPhone.xcstrings b/VoidLink/mul.lproj/iPhone.xcstrings index c0f5c5ed..60f40812 100644 --- a/VoidLink/mul.lproj/iPhone.xcstrings +++ b/VoidLink/mul.lproj/iPhone.xcstrings @@ -2299,6 +2299,36 @@ } } }, + "Ldr-kv-d7n.text" : { + "comment" : "Class = \"UILabel\"; text = \"Haptic Engine\"; ObjectID = \"Ldr-kv-d7n\";", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Haptic Engine" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "振动引擎" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "振動引擎" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "振動引擎" + } + } + } + }, "LVq-7c-3zg.text" : { "comment" : "Class = \"UILabel\"; text = \"Performance Graph \"; ObjectID = \"LVq-7c-3zg\";", "extractionState" : "extracted_with_value", @@ -2521,6 +2551,126 @@ } } }, + "nj6-9l-1j7.segmentTitles[0]" : { + "comment" : "Class = \"UISegmentedControl\"; nj6-9l-1j7.segmentTitles[0] = \"Auto\"; ObjectID = \"nj6-9l-1j7\";", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Auto" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "自动" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "自動" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "自動" + } + } + } + }, + "nj6-9l-1j7.segmentTitles[1]" : { + "comment" : "Class = \"UISegmentedControl\"; nj6-9l-1j7.segmentTitles[1] = \"Built-in\"; ObjectID = \"nj6-9l-1j7\";", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Built-in" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "内置" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "内建" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "内建" + } + } + } + }, + "nj6-9l-1j7.segmentTitles[2]" : { + "comment" : "Class = \"UISegmentedControl\"; nj6-9l-1j7.segmentTitles[2] = \"L/R Swapped\"; ObjectID = \"nj6-9l-1j7\";", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "L/R Swapped" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "左右交换" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "左右交換" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "左右交換" + } + } + } + }, + "nj6-9l-1j7.segmentTitles[3]" : { + "comment" : "Class = \"UISegmentedControl\"; nj6-9l-1j7.segmentTitles[3] = \"Disabled\"; ObjectID = \"nj6-9l-1j7\";", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Disabled" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "关闭" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "關閉" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "關閉" + } + } + } + }, "NVL-ZX-kMQ.text" : { "comment" : "Class = \"UILabel\"; text = \"Soft Keyboard Toolbar\"; ObjectID = \"NVL-ZX-kMQ\";", "extractionState" : "extracted_with_value",