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",