From 49b03e65b2aae4a6254ba0dc1ea17b09c3daa958 Mon Sep 17 00:00:00 2001 From: True Zhuanjia Date: Thu, 26 Feb 2026 17:18:21 +0800 Subject: [PATCH] feat: hover mode selector --- VoidLink/Base.lproj/iPad.storyboard | 34 ++-- VoidLink/Input/CustomOSC/OSCProfile.h | 11 +- VoidLink/Input/CustomOSC/OSCProfile.m | 6 +- VoidLink/Input/PencilHandler.swift | 10 +- VoidLink/Input/StreamView.m | 32 +++- .../VoidLink v1.0.xcdatamodel/contents | 2 +- VoidLink/Localization/Localizable.xcstrings | 58 +++---- .../ViewControllers/SettingsViewController.h | 5 +- .../ViewControllers/SettingsViewController.m | 14 +- VoidLink/mul.lproj/iPad.xcstrings | 152 +++++++++++++++++- 10 files changed, 257 insertions(+), 67 deletions(-) diff --git a/VoidLink/Base.lproj/iPad.storyboard b/VoidLink/Base.lproj/iPad.storyboard index 3c88b027..7855efd9 100644 --- a/VoidLink/Base.lproj/iPad.storyboard +++ b/VoidLink/Base.lproj/iPad.storyboard @@ -2088,24 +2088,30 @@ - - + + - - + + @@ -2123,8 +2129,6 @@ - - @@ -2188,6 +2192,8 @@ + + diff --git a/VoidLink/Input/CustomOSC/OSCProfile.h b/VoidLink/Input/CustomOSC/OSCProfile.h index 064d0c99..2818ac20 100644 --- a/VoidLink/Input/CustomOSC/OSCProfile.h +++ b/VoidLink/Input/CustomOSC/OSCProfile.h @@ -22,6 +22,14 @@ typedef NS_ENUM(NSInteger, MapGyroTo) { driftCorrection }; +typedef NS_ENUM(NSInteger, PencilHoverMode) { + HoverDisabled, + HoverPencil, + HoverMouse, + HoverBoth +}; + + /** This object contains information pertaining to any of the user created, custom on screen controller layout configurations, or 'profiles.' The object contains a 'name' property for easy reference, as well as an 'isSelected' property which is used to determine whether this particular custom OSC layout should show on screen during game stream view. Only one 'OSCProfile' is set to 'isSelected' at any given time. The object also contains an array of 'OnScreenButtonStates' which provides information that allows us to move and hide/unhide each of the 19 on screen buttons. Note that the 'buttonStates' property should contain an NSMutableArray of ENCODED 'OnScreenButtonState' objects. This allows us to save the 'OSCProfile' object to NSUserDefaults. Additionally the 'OSCProfile' object adopts encoding and decoding protocols so that we can encode the object before saving it to NSUserDefaults. By saving this object to NSUserDefaults we allow the user to save and load their custom on screen controller button layouts between app launches @@ -58,9 +66,10 @@ typedef NS_ENUM(NSInteger, MapGyroTo) { @property (nonatomic, assign) bool squeezeShorcutEnabled; @property NSString *squeezeStartShortcut; @property NSString *squeezeEndShortcut; -@property (nonatomic, assign) bool autoPencilHoverTermination; @property (nonatomic, assign) bool pencilPausesNativeTouch; @property (nonatomic, assign) bool disablePencilSlideGestures; +@property (nonatomic, assign) PencilHoverMode pencilHoverMode; + - (id) initWithName:(NSString*)name buttonStates:(NSMutableArray*)buttonStates isSelected:(BOOL)isSelected; diff --git a/VoidLink/Input/CustomOSC/OSCProfile.m b/VoidLink/Input/CustomOSC/OSCProfile.m index cf9d227f..2901a022 100644 --- a/VoidLink/Input/CustomOSC/OSCProfile.m +++ b/VoidLink/Input/CustomOSC/OSCProfile.m @@ -59,9 +59,9 @@ [encoder encodeBool:self.squeezeShorcutEnabled forKey:@"squeezeShorcutEnabled"]; [encoder encodeObject:self.squeezeStartShortcut forKey:@"squeezeStartShortcut"]; [encoder encodeObject:self.squeezeEndShortcut forKey:@"squeezeEndShortcut"]; - [encoder encodeBool:self.autoPencilHoverTermination forKey:@"autoPencilHoverTermination"]; [encoder encodeBool:self.pencilPausesNativeTouch forKey:@"pencilPausesNativeTouch"]; [encoder encodeBool:self.disablePencilSlideGestures forKey:@"disablePencilSlideGestures"]; + [encoder encodeInt64:self.pencilHoverMode forKey:@"pencilHoverMode"]; } - (id) initWithCoder:(NSCoder*)decoder { @@ -159,7 +159,7 @@ self.pencilPausesNativeTouch = [decoder containsValueForKey:@"pencilPausesNativeTouch"] ? [decoder decodeBoolForKey:@"pencilPausesNativeTouch"] : false; self.disablePencilSlideGestures = [decoder containsValueForKey:@"disablePencilSlideGestures"] ? [decoder decodeBoolForKey:@"disablePencilSlideGestures"] : false; - self.autoPencilHoverTermination = [decoder containsValueForKey:@"autoPencilHoverTermination"] ? [decoder decodeBoolForKey:@"autoPencilHoverTermination"] : false; + self.pencilHoverMode = [decoder containsValueForKey:@"pencilHoverMode"] ? [decoder decodeInt64ForKey:@"pencilHoverMode"] : HoverPencil; } return self; @@ -199,7 +199,7 @@ copy.squeezeEndShortcut = [self.squeezeEndShortcut mutableCopy]; // NSString → NSMutableString copy.pencilPausesNativeTouch = self.pencilPausesNativeTouch; copy.disablePencilSlideGestures = self.disablePencilSlideGestures; - copy.autoPencilHoverTermination = self.autoPencilHoverTermination; + copy.pencilHoverMode = self.pencilHoverMode; return copy; } diff --git a/VoidLink/Input/PencilHandler.swift b/VoidLink/Input/PencilHandler.swift index 302fe0f0..4d204416 100644 --- a/VoidLink/Input/PencilHandler.swift +++ b/VoidLink/Input/PencilHandler.swift @@ -23,7 +23,8 @@ import UIKit private var pressureCurveEnabled: Bool = false private var manualHoverFlag: Bool = false @objc static var hoverSupported: Bool = false - @objc static private(set) var autoHoverTermination: Bool = false + // @objc static private(set) var autoHoverTermination: Bool = false + @objc static private(set) var hoverMode: PencilHoverMode = .HoverPencil private(set) var pencilProEnabled: Bool = false private var isFirstMove: Bool = false private var moveEventIndex: Int64 = 0 @@ -78,7 +79,8 @@ import UIKit print("squeezeShorcutEnabled \(CACurrentMediaTime())") } - PencilHandler.autoHoverTermination = selectedProfile.autoPencilHoverTermination + // PencilHandler.autoHoverTermination = selectedProfile.autoPencilHoverTermination + PencilHandler.hoverMode = selectedProfile.pencilHoverMode pressureCurveEnabled = selectedProfile.pressureCurveEnabled @@ -227,7 +229,7 @@ import UIKit eventType = UInt8(LI_TOUCH_EVENT_MOVE) } - delay = manualTick ? 0.0086 : 0 + delay = manualTick ? 0.0086 : 0.0086 delay = eventType == UInt8(LI_TOUCH_EVENT_UP) ? delay : 0 if previousTimeStamp == 0 { @@ -252,7 +254,7 @@ import UIKit if eventType == UInt8(LI_TOUCH_EVENT_UP) { PencilHandler.isDrawing = false - if PencilHandler.autoHoverTermination || !PencilHandler.hoverSupported { + if PencilHandler.hoverMode == .HoverDisabled || !PencilHandler.hoverSupported { LiSendPenEvent(UInt8(LI_TOUCH_EVENT_HOVER), UInt8(LI_TOOL_TYPE_PEN), 0, Float(normalizedLocation.x), Float(normalizedLocation.y), 0, 0, 0, self.getRotation(fromAzimuthAngle: Float(azimuth)), self.getTilt(fromAltitudeAngle: Float(altitude))) DispatchQueue.global().asyncAfter(deadline: .now() + 0.0086){ if !PencilHandler.isDrawing { diff --git a/VoidLink/Input/StreamView.m b/VoidLink/Input/StreamView.m index 2ff2f412..f14e7e38 100644 --- a/VoidLink/Input/StreamView.m +++ b/VoidLink/Input/StreamView.m @@ -213,6 +213,7 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5; isPencilHovering = false; } + #if defined(__IPHONE_16_1) || defined(__TVOS_16_1) if (@available(iOS 16.1, *)) { UIHoverGestureRecognizer *stylusHoverRecognizer = [[UIHoverGestureRecognizer alloc] initWithTarget:self action:@selector(sendStylusHoverEvent:)]; @@ -796,17 +797,20 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5; - (void)sendStylusHoverEvent:(UIHoverGestureRecognizer*)gesture API_AVAILABLE(ios(13.0)) { uint8_t type; - + switch (gesture.state) { case UIGestureRecognizerStateBegan: case UIGestureRecognizerStateChanged: type = LI_TOUCH_EVENT_HOVER; isPencilHovering = true; if(!PencilHandler.hoverSupported) PencilHandler.hoverSupported = true; + // if(gesture.state == UIGestureRecognizerStateChanged) NSLog(@"UIGestureRecognizerStateChanged %f", CACurrentMediaTime()); + break; case UIGestureRecognizerStateEnded: type = LI_TOUCH_EVENT_HOVER_LEAVE; + // NSLog(@"UIGestureRecognizerStateEnded %f", CACurrentMediaTime()); break; default: return; @@ -838,8 +842,25 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5; } #endif - if(!PencilHandler.autoHoverTermination) LiSendPenEvent(type, LI_TOOL_TYPE_PEN, 0, location.x / videoSize.width, location.y / videoSize.height, - distance, 0.0f, 0.0f, rotationAngle, tiltAngle); + + dispatch_after(0, dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE,0), ^{// Code to execute after the delay + if(PencilHandler.isDrawing) return; + switch (PencilHandler.hoverMode) { + case HoverPencil: + LiSendPenEvent(type, LI_TOOL_TYPE_PEN, 0, location.x / videoSize.width, location.y / videoSize.height, distance, 0.0f, 0.0f, rotationAngle, tiltAngle); + break; + case HoverMouse: + [self updateCursorLocation:location isMouse:YES]; + break; + case HoverDisabled: + break; + case HoverBoth: + LiSendPenEvent(type, LI_TOOL_TYPE_PEN, 0, location.x / videoSize.width, location.y / videoSize.height, distance, 0.0f, 0.0f, rotationAngle, tiltAngle); + [self updateCursorLocation:location isMouse:YES]; + default: + break; + } + }); } #endif @@ -1225,7 +1246,10 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5; // Move the cursor on the host if no buttons are pressed. // Motion with buttons pressed in handled in touchesMoved: if (lastMouseButtonMask == 0) { - if(!isPencilHovering) [self updateCursorLocation:request.location isMouse:YES]; + dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.001 * NSEC_PER_SEC)); + dispatch_after(delayTime, dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE,0), ^{ + if(!self->isPencilHovering) [self updateCursorLocation:request.location isMouse:YES]; + }); } // The pointer interaction should cover the video region only diff --git a/VoidLink/Limelight.xcdatamodeld/VoidLink v1.0.xcdatamodel/contents b/VoidLink/Limelight.xcdatamodeld/VoidLink v1.0.xcdatamodel/contents index 4aeb53ba..0cf2ebbe 100644 --- a/VoidLink/Limelight.xcdatamodeld/VoidLink v1.0.xcdatamodel/contents +++ b/VoidLink/Limelight.xcdatamodeld/VoidLink v1.0.xcdatamodel/contents @@ -102,7 +102,7 @@ - + diff --git a/VoidLink/Localization/Localizable.xcstrings b/VoidLink/Localization/Localizable.xcstrings index fcc9d470..46dea5d4 100644 --- a/VoidLink/Localization/Localizable.xcstrings +++ b/VoidLink/Localization/Localizable.xcstrings @@ -1594,35 +1594,6 @@ } } }, - "autoHoverTerminationStackTip" : { - "extractionState" : "manual", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Resolves compatibility issues with certain PC software (such as SAI2) that cannot function properly without hover state." - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "needs_review", - "value" : "用于解决部分PC软件(如SAI2)没有悬浮状态则无法正常使用的问题。" - } - }, - "zh-Hant" : { - "stringUnit" : { - "state" : "translated", - "value" : "此功能用於解決部分 PC 軟體(例如 SAI2)在沒有懸浮狀態時無法正常使用的問題。" - } - }, - "zh-HK" : { - "stringUnit" : { - "state" : "translated", - "value" : "此功能用於解決部分 PC 軟體(例如 SAI2)在沒有懸浮狀態時無法正常使用的問題。" - } - } - } - }, "Autotap timer " : { "extractionState" : "manual", "localizations" : { @@ -4084,6 +4055,35 @@ } } }, + "hoverModeStackTip" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "When “both” is selected, you’ll get flickering cursor on host.\nThis is not a Pencil Pro Pack function." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "当选择“同时悬停”时,PC端的光标将会闪烁。\n此功能是Pencil基础功能的一部分,不属于Pencil Pro Pack。 " + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "當選擇「同時懸停」時,PC 端的游標將會閃爍。\n此功能為 Pencil 基礎功能的一部分,不屬於 Pencil Pro Pack。" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "當選擇「同時懸停」時,PC 端的游標將會閃爍。\n此功能為 Pencil 基礎功能的一部分,不屬於 Pencil Pro Pack。" + } + } + } + }, "Important Tips" : { "extractionState" : "manual", "localizations" : { diff --git a/VoidLink/ViewControllers/SettingsViewController.h b/VoidLink/ViewControllers/SettingsViewController.h index 826db3c2..ba1fcec3 100644 --- a/VoidLink/ViewControllers/SettingsViewController.h +++ b/VoidLink/ViewControllers/SettingsViewController.h @@ -281,8 +281,9 @@ @property (weak, nonatomic) IBOutlet UIStackView *disablePencilSlideGestureStack; @property (weak, nonatomic) IBOutlet UISwitch *disablePencilSlideGestureSwitch; -@property (weak, nonatomic) IBOutlet UIStackView *autoHoverTerminationStack; -@property (weak, nonatomic) IBOutlet UISwitch *autoHoverTerminationSwitch; +@property (weak, nonatomic) IBOutlet UIStackView *hoverModeStack; +@property (weak, nonatomic) IBOutlet UISegmentedControl *hoverModeSelector; + #pragma clang diagnostic push diff --git a/VoidLink/ViewControllers/SettingsViewController.m b/VoidLink/ViewControllers/SettingsViewController.m index 96e736a0..d9e98406 100644 --- a/VoidLink/ViewControllers/SettingsViewController.m +++ b/VoidLink/ViewControllers/SettingsViewController.m @@ -395,7 +395,7 @@ BOOL isCustomResolution(int resolutionSelected) { [self.squeezeShortcutSwitch setOn:oscProfile.squeezeShorcutEnabled]; [self.pencilPausesNativeTouchSwitch setOn:oscProfile.pencilPausesNativeTouch]; [self.disablePencilSlideGestureSwitch setOn:oscProfile.disablePencilSlideGestures]; - [self.autoHoverTerminationSwitch setOn:oscProfile.autoPencilHoverTermination]; + self.hoverModeSelector.selectedSegmentIndex = oscProfile.pencilHoverMode; } - (void)saveGameProfileConfigs{ @@ -421,7 +421,7 @@ BOOL isCustomResolution(int resolutionSelected) { && oscProfile.squeezeShorcutEnabled == self.squeezeShortcutSwitch.isOn && oscProfile.pencilPausesNativeTouch == self.pencilPausesNativeTouchSwitch.isOn && oscProfile.disablePencilSlideGestures == self.disablePencilSlideGestureSwitch.isOn - && oscProfile.autoPencilHoverTermination == self.autoHoverTerminationSwitch.isOn + && oscProfile.pencilHoverMode == self.hoverModeSelector.selectedSegmentIndex ); if(!configNotChanged){ @@ -443,7 +443,7 @@ BOOL isCustomResolution(int resolutionSelected) { oscProfile.squeezeShorcutEnabled = self.squeezeShortcutSwitch.isOn; oscProfile.pencilPausesNativeTouch = self.pencilPausesNativeTouchSwitch.isOn; oscProfile.disablePencilSlideGestures = self.disablePencilSlideGestureSwitch.isOn; - oscProfile.autoPencilHoverTermination = self.autoHoverTerminationSwitch.isOn; + oscProfile.pencilHoverMode = self.hoverModeSelector.selectedSegmentIndex; [oscProfileMan replaceSelectedProfileWith:oscProfile overwriteDefault:YES]; if(PencilHandler.shared) [PencilHandler.shared setupPressureLUTWithProfile:oscProfile]; } @@ -889,7 +889,7 @@ BOOL isCustomResolution(int resolutionSelected) { [self addSetting:self.pressureCurveStack ofId:@"pressureCurveStack" withInfoTag:NO withDynamicLabel:NO to:pencilSection]; [self addSetting:self.doubleTapShortcutStack ofId:@"doubleTapShortcutStack" withInfoTag:YES withDynamicLabel:NO to:pencilSection]; [self addSetting:self.squeezeShortcutStack ofId:@"squeezeShortcutStack" withInfoTag:YES withDynamicLabel:NO to:pencilSection]; - [self addSetting:self.autoHoverTerminationStack ofId:@"autoHoverTerminationStack" withInfoTag:NO withDynamicLabel:NO to:pencilSection]; + [self addSetting:self.hoverModeStack ofId:@"hoverModeStack" withInfoTag:YES withDynamicLabel:NO to:pencilSection]; [self addSetting:self.pencilPausesNativeTouchStack ofId:@"pencilPausesNativeTouchStack" withInfoTag:NO withDynamicLabel:NO to:pencilSection]; [self addSetting:self.disablePencilSlideGestureStack ofId:@"disablePencilSlideGestureStack" withInfoTag:NO withDynamicLabel:NO to:pencilSection]; [pencilSection addToParentStack:_parentStack]; @@ -1525,8 +1525,8 @@ BOOL isCustomResolution(int resolutionSelected) { tipText = [LocalizationHelper localizedStringForKey:@"asyncFrameDequeueStackTip"]; showOnlineDocAction = false; } - if([sender.superview.accessibilityIdentifier isEqualToString: @"autoHoverTerminationStack"]){ - tipText = [LocalizationHelper localizedStringForKey:@"autoHoverTerminationStackTip"]; + if([sender.superview.accessibilityIdentifier isEqualToString: @"hoverModeStack"]){ + tipText = [LocalizationHelper localizedStringForKey:@"hoverModeStackTip"]; showOnlineDocAction = false; } @@ -3034,7 +3034,6 @@ BOOL isCustomResolution(int resolutionSelected) { [[NSUserDefaults standardUserDefaults] synchronize]; NSInteger fps = [self getChosenFrameRate]; - [self.touchMoveEventIntervalSlider setValue: fps>60 ? 1/((CGFloat)fps)*0.5*1000000-1500 : 3530]; [self touchMoveEventIntervalSliderMoved:self.touchMoveEventIntervalSlider]; [self updateBitrate]; } @@ -3447,7 +3446,6 @@ BOOL isCustomResolution(int resolutionSelected) { [self.squeezeShortcutSwitch setOn:false]; [self.pencilPausesNativeTouchSwitch setOn:false]; [self.disablePencilSlideGestureSwitch setOn:false]; - [self.autoHoverTerminationSwitch setOn:false]; NSNumber *value = notification.userInfo[@"interruption"]; diff --git a/VoidLink/mul.lproj/iPad.xcstrings b/VoidLink/mul.lproj/iPad.xcstrings index c762516d..20b46aa1 100644 --- a/VoidLink/mul.lproj/iPad.xcstrings +++ b/VoidLink/mul.lproj/iPad.xcstrings @@ -2257,6 +2257,36 @@ } } }, + "DhB-EF-mDt.text" : { + "comment" : "Class = \"UILabel\"; text = \"Hover Mode\"; ObjectID = \"DhB-EF-mDt\";", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Hover Mode" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "悬停模式" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "懸停模式" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "懸停模式" + } + } + } + }, "dOF-FW-cBU.text" : { "comment" : "Class = \"UILabel\"; text = \" \"; ObjectID = \"dOF-FW-cBU\";", "extractionState" : "extracted_with_value", @@ -4749,7 +4779,7 @@ }, "q78-aY-mIe.text" : { "comment" : "Class = \"UILabel\"; text = \"Disable Hovering\"; ObjectID = \"q78-aY-mIe\";", - "extractionState" : "extracted_with_value", + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -5293,6 +5323,126 @@ } } }, + "t12-bo-icS.segmentTitles[0]" : { + "comment" : "Class = \"UISegmentedControl\"; t12-bo-icS.segmentTitles[0] = \"Disabled\"; ObjectID = \"t12-bo-icS\";", + "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" : "關閉" + } + } + } + }, + "t12-bo-icS.segmentTitles[1]" : { + "comment" : "Class = \"UISegmentedControl\"; t12-bo-icS.segmentTitles[1] = \"Pencil\"; ObjectID = \"t12-bo-icS\";", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Pencil" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pencil" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pencil" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pencil" + } + } + } + }, + "t12-bo-icS.segmentTitles[2]" : { + "comment" : "Class = \"UISegmentedControl\"; t12-bo-icS.segmentTitles[2] = \"Mouse\"; ObjectID = \"t12-bo-icS\";", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Mouse" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "鼠标" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "滑鼠" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "滑鼠" + } + } + } + }, + "t12-bo-icS.segmentTitles[3]" : { + "comment" : "Class = \"UISegmentedControl\"; t12-bo-icS.segmentTitles[3] = \"Both\"; ObjectID = \"t12-bo-icS\";", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Both" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "同时悬停" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "同時懸停" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "同時懸停" + } + } + } + }, "tAd-bm-2IO.text" : { "comment" : "Class = \"UILabel\"; text = \"Local Mouse Pointer\"; ObjectID = \"tAd-bm-2IO\";", "extractionState" : "extracted_with_value",