diff --git a/Limelight/ViewControllers/SettingsViewController.h b/Limelight/ViewControllers/SettingsViewController.h
index 7d9b4b49..305c1cf8 100644
--- a/Limelight/ViewControllers/SettingsViewController.h
+++ b/Limelight/ViewControllers/SettingsViewController.h
@@ -32,8 +32,7 @@
@property (strong, nonatomic) IBOutlet UISegmentedControl *yuv444Selector;
@property (strong, nonatomic) IBOutlet UISegmentedControl *pipSelector;
@property (strong, nonatomic) IBOutlet UISegmentedControl *hdrSelector;
-@property (strong, nonatomic) IBOutlet UILabel *frameQueueSizeLabel;
-@property (strong, nonatomic) IBOutlet UISlider *frameQueueSizeSlider;
+@property (strong, nonatomic) IBOutlet UISegmentedControl *framePacingSelector;
@property (strong, nonatomic) IBOutlet UISegmentedControl *btMouseSelector;
@property (strong, nonatomic) IBOutlet UISegmentedControl *reverseMouseWheelDirectionSelector;
@property (strong, nonatomic) IBOutlet UISegmentedControl *statsOverlaySelector;
@@ -63,10 +62,6 @@
@property (strong, nonatomic) IBOutlet UISegmentedControl *externalDisplayModeSelector;
@property (strong, nonatomic) IBOutlet UISegmentedControl *localMousePointerModeSelector;
-@property (strong, nonatomic) IBOutlet UILabel *enableGraphsLabel;
-@property (strong, nonatomic) IBOutlet UISegmentedControl *enableGraphsSelector;
-@property (strong, nonatomic) IBOutlet UIStepper *graphOpacityStepper;
-@property (strong, nonatomic) IBOutlet UISegmentedControl *renderingBackendSelector;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunguarded-availability"
diff --git a/Limelight/ViewControllers/SettingsViewController.m b/Limelight/ViewControllers/SettingsViewController.m
index 117bfc04..97f9e805 100644
--- a/Limelight/ViewControllers/SettingsViewController.m
+++ b/Limelight/ViewControllers/SettingsViewController.m
@@ -22,8 +22,6 @@
bool justEnteredSettingsViewDoNotOpenOscLayoutTool;
uint16_t oscLayoutFingers;
CustomEdgeSlideGestureRecognizer *slideToCloseSettingsViewRecognizer;
- NSInteger _frameQueueSize;
- NSInteger _graphOpacity;
}
@dynamic overrideUserInterfaceStyle;
@@ -201,7 +199,7 @@ BOOL isCustomResolution(CGSize res) {
}
for (int i = 0; i < RESOLUTION_TABLE_CUSTOM_INDEX; i++) {
-
+
if ((res.width == resolutionTable[i].width && res.height == resolutionTable[i].height) || (res.height == resolutionTable[i].width && res.width == resolutionTable[i].height)) {
return NO;
}
@@ -217,7 +215,7 @@ BOOL isCustomResolution(CGSize res) {
- (bool)isFullScreenRequired {
NSDictionary *infoDictionary = [[NSBundle mainBundle] infoDictionary];
NSNumber *requiresFullScreen = infoDictionary[@"UIRequiresFullScreen"];
-
+
if (requiresFullScreen != nil) {
return [requiresFullScreen boolValue];
}
@@ -242,7 +240,7 @@ BOOL isCustomResolution(CGSize res) {
appWindowHeight = bounds.size.height * screenScale;
}
bool needSwapWidthAndHeight = appWindowWidth > appWindowHeight;
-
+
resolutionTable[4] = CGSizeMake(safeAreaWidth, appWindowHeight);
for(uint8_t i=0;i<7;i++){
@@ -342,7 +340,7 @@ BOOL isCustomResolution(CGSize res) {
- (void)viewDidLoad {
//[self pushDownExistingWidgets];
//[self addExitButtonOnTop];
-
+
self->slideToCloseSettingsViewRecognizer = [[CustomEdgeSlideGestureRecognizer alloc] initWithTarget:self action:@selector(edgeSwiped)];
slideToCloseSettingsViewRecognizer.edges = UIRectEdgeLeft;
slideToCloseSettingsViewRecognizer.normalizedThresholdDistance = 0.0;
@@ -351,13 +349,13 @@ BOOL isCustomResolution(CGSize res) {
slideToCloseSettingsViewRecognizer.delaysTouchesBegan = NO;
slideToCloseSettingsViewRecognizer.delaysTouchesEnded = NO;
[self.view addGestureRecognizer:slideToCloseSettingsViewRecognizer];
-
+
justEnteredSettingsViewDoNotOpenOscLayoutTool = true;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(deviceOrientationDidChange) // handle orientation change since i made portrait mode available
name:UIDeviceOrientationDidChangeNotification
object:nil];
-
+
// Always run settings in dark mode because we want the light fonts
if (@available(iOS 13.0, tvOS 13.0, *)) {
self.overrideUserInterfaceStyle = UIUserInterfaceStyleDark;
@@ -368,8 +366,6 @@ BOOL isCustomResolution(CGSize res) {
// Ensure we pick a bitrate that falls exactly onto a slider notch
_bitrate = bitrateTable[[self getSliderValueForBitrate:[currentSettings.bitrate intValue]]];
- _frameQueueSize = [currentSettings.frameQueueSize intValue];
- _graphOpacity = [currentSettings.graphOpacity intValue];
// Get the size of the screen with and without safe area insets
UIWindow *window = UIApplication.sharedApplication.windows.firstObject;
@@ -383,26 +379,20 @@ BOOL isCustomResolution(CGSize res) {
UITapGestureRecognizer *resolutionDisplayViewTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(resolutionDisplayViewTapped:)];
[self.resolutionDisplayView addGestureRecognizer:resolutionDisplayViewTap];
- resolutionTable[0] = CGSizeMake(1280, 720);
- resolutionTable[1] = CGSizeMake(1920, 1080);
- resolutionTable[2] = CGSizeMake(2560, 1440);
+ resolutionTable[0] = CGSizeMake(640, 360);
+ resolutionTable[1] = CGSizeMake(1280, 720);
+ resolutionTable[2] = CGSizeMake(1920, 1080);
resolutionTable[3] = CGSizeMake(3840, 2160);
resolutionTable[4] = CGSizeMake(safeAreaWidth, fullScreenHeight);
resolutionTable[5] = CGSizeMake(fullScreenWidth, fullScreenHeight);
resolutionTable[6] = CGSizeMake([currentSettings.width integerValue], [currentSettings.height integerValue]); // custom initial value
[self updateResolutionTable];
-
+
// Don't populate the custom entry unless we have a custom resolution
if (!isCustomResolution(resolutionTable[6])) {
resolutionTable[6] = CGSizeMake(0, 0);
}
-
- // Customize framerate list for ProMotion devices
- if ([[UIScreen mainScreen] maximumFramesPerSecond] >= 90) {
- [self.framerateSelector setEnabled:YES forSegmentAtIndex:2]; // enable 90
- [self.framerateSelector setEnabled:YES forSegmentAtIndex:3]; // enable 120
- }
-
+
NSInteger framerate;
switch ([currentSettings.framerate integerValue]) {
case 30:
@@ -412,11 +402,8 @@ BOOL isCustomResolution(CGSize res) {
case 60:
framerate = 1;
break;
- case 90:
- framerate = 2;
- break;
case 120:
- framerate = 3;
+ framerate = 2;
break;
}
@@ -425,6 +412,17 @@ BOOL isCustomResolution(CGSize res) {
resolution = 0;
}
+ // Only show the 120 FPS option if we have a > 60-ish Hz display
+ bool enable120Fps = false;
+ if (@available(iOS 10.3, tvOS 10.3, *)) {
+ if ([UIScreen mainScreen].maximumFramesPerSecond > 62) {
+ enable120Fps = true;
+ }
+ }
+ if (!enable120Fps) {
+ [self.framerateSelector removeSegmentAtIndex:2 animated:NO];
+ }
+
// Disable codec selector segments for unsupported codecs
#if defined(__IPHONE_16_0) || defined(__TVOS_16_0)
if (!VTIsHardwareDecodeSupported(kCMVideoCodecType_AV1))
@@ -465,27 +463,13 @@ BOOL isCustomResolution(CGSize res) {
else {
[self.hdrSelector setSelectedSegmentIndex:currentSettings.enableHdr ? 1 : 0];
}
-
+
[self.yuv444Selector setSelectedSegmentIndex:currentSettings.enableYUV444 ? 1 : 0];
[self.pipSelector setSelectedSegmentIndex:currentSettings.enablePIP ? 1 : 0];
[self.statsOverlaySelector setSelectedSegmentIndex:currentSettings.statsOverlayLevel.intValue];
- NSInteger renderingBackend = [currentSettings.renderingBackend integerValue];
- [self.renderingBackendSelector setSelectedSegmentIndex:renderingBackend];
- [self.enableGraphsSelector setSelectedSegmentIndex:currentSettings.enableGraphs ? 1 : 0];
- [self.enableGraphsSelector addTarget:self action:@selector(enableGraphsChanged) forControlEvents:UIControlEventValueChanged];
- [self enableGraphsChanged];
- [self.graphOpacityStepper setMinimumValue:0];
- [self.graphOpacityStepper setMaximumValue:100];
- [self.graphOpacityStepper setValue:_graphOpacity];
- [self.graphOpacityStepper addTarget:self action:@selector(graphOpacityStepperMoved) forControlEvents:UIControlEventValueChanged];
- [self updateGraphOpacityText];
[self.btMouseSelector setSelectedSegmentIndex:currentSettings.btMouseSupport ? 1 : 0];
[self.optimizeSettingsSelector setSelectedSegmentIndex:currentSettings.optimizeGames ? 1 : 0];
- [self.frameQueueSizeSlider setMinimumValue:1];
- [self.frameQueueSizeSlider setMaximumValue:5];
- [self.frameQueueSizeSlider setValue:_frameQueueSize];
- [self.frameQueueSizeSlider addTarget:self action:@selector(frameQueueSizeSliderMoved) forControlEvents:UIControlEventValueChanged];
- [self updateFrameQueueSizeText];
+ [self.framePacingSelector setSelectedSegmentIndex:currentSettings.useFramePacing ? 1 : 0];
[self.multiControllerSelector setSelectedSegmentIndex:currentSettings.multiController ? 1 : 0];
[self.swapABXYButtonsSelector setSelectedSegmentIndex:currentSettings.swapABXYButtons ? 1 : 0];
[self.audioOnPCSelector setSelectedSegmentIndex:currentSettings.playAudioOnPC ? 1 : 0];
@@ -502,20 +486,20 @@ BOOL isCustomResolution(CGSize res) {
[self.bitrateSlider addTarget:self action:@selector(bitrateSliderMoved) forControlEvents:UIControlEventValueChanged];
[self updateBitrateText];
[self updateResolutionDisplayViewText];
-
+
// Unlock Display Orientation setting
bool unlockDisplayOrientationSelectorEnabled = [self isFullScreenRequired];//need "requires fullscreen" enabled in the app bunddle to make runtime orientation limitation woring
if(unlockDisplayOrientationSelectorEnabled) [self.unlockDisplayOrientationSelector setSelectedSegmentIndex:currentSettings.unlockDisplayOrientation ? 1 : 0];
else [self.unlockDisplayOrientationSelector setSelectedSegmentIndex:1]; // can't lock screen orientation in this mode = Display Orientation always unlocked
[self.unlockDisplayOrientationSelector setEnabled:unlockDisplayOrientationSelectorEnabled];
-
+
// lift streamview setting
[self.liftStreamViewForKeyboardSelector setSelectedSegmentIndex:currentSettings.liftStreamViewForKeyboard ? 1 : 0];// Load old setting
-
+
// showkeyboard toolbar setting
[self.showKeyboardToolbarSelector setSelectedSegmentIndex:currentSettings.showKeyboardToolbar ? 1 : 0];// Load old setting
-
+
// reverse mouse wheel direction setting
[self.reverseMouseWheelDirectionSelector setSelectedSegmentIndex:currentSettings.reverseMouseWheelDirection ? 1 : 0];// Load old setting
@@ -529,11 +513,11 @@ BOOL isCustomResolution(CGSize res) {
[self.slideToMenuDistanceSlider setValue:currentSettings.slideToSettingsDistance.floatValue];
[self.slideToMenuDistanceSlider addTarget:self action:@selector(slideToMenuDistanceSliderMoved) forControlEvents:(UIControlEventValueChanged)]; // Update label display when slider is being moved.
[self slideToMenuDistanceSliderMoved];
+
-
-
+
//TouchMode & OSC Related Settings:
-
+
// pointer veloc setting, will be enable/disabled by touchMode
[self.pointerVelocityModeDividerSlider setValue: (uint8_t)(currentSettings.pointerVelocityModeDivider.floatValue * 100) animated:YES]; // Load old setting.
[self.pointerVelocityModeDividerSlider addTarget:self action:@selector(pointerVelocityModeDividerSliderMoved) forControlEvents:(UIControlEventValueChanged)]; // Update label display when slider is being moved.
@@ -543,13 +527,13 @@ BOOL isCustomResolution(CGSize res) {
[self.touchPointerVelocityFactorSlider setValue: [self map_SliderValue_fromVelocFactor: currentSettings.touchPointerVelocityFactor.floatValue] animated:YES]; // Load old setting.
[self.touchPointerVelocityFactorSlider addTarget:self action:@selector(touchPointerVelocityFactorSliderMoved) forControlEvents:(UIControlEventValueChanged)]; // Update label display when slider is being moved.
[self touchPointerVelocityFactorSliderMoved];
-
+
// init relative touch mouse pointer veloc setting, will be enable/disabled by touchMode
[self.mousePointerVelocityFactorSlider setValue:[self map_SliderValue_fromVelocFactor: currentSettings.mousePointerVelocityFactor.floatValue] animated:YES]; // Load old setting.
[self.mousePointerVelocityFactorSlider addTarget:self action:@selector(mousePointerVelocityFactorSliderMoved) forControlEvents:(UIControlEventValueChanged)]; // Update label display when slider is being moved.
[self mousePointerVelocityFactorSliderMoved];
-
-
+
+
// these settings will be affected by onscreenControl & touchMode, must be loaded before them.
// NSLog(@"osc tool fingers setting test: %d", currentSettings.oscLayoutToolFingers.intValue);
self->oscLayoutFingers = (uint16_t)currentSettings.oscLayoutToolFingers.intValue; // load old setting of oscLayoutFingers
@@ -563,7 +547,7 @@ BOOL isCustomResolution(CGSize res) {
[self.onscreenControlSelector addTarget:self action:@selector(onscreenControlChanged) forControlEvents:UIControlEventValueChanged];
[self onscreenControlChanged];
[self.largerStickLR1Selector setSelectedSegmentIndex:currentSettings.largerStickLR1 ? 1 : 0]; // load old setting of largerStickLR1
-
+
// tap exclusion area size for custom OSC, must be loaded before touchMode & osc selector.
[self.oscTapExlusionAreaSizeSlider setValue:currentSettings.oscTapExlusionAreaSize.floatValue * 100 animated:YES]; // Load old setting.
[self.oscTapExlusionAreaSizeSlider addTarget:self action:@selector(oscTapExlusionAreaSizeSliderMoved) forControlEvents:(UIControlEventValueChanged)]; // Update label display when slider is being moved.
@@ -576,7 +560,7 @@ BOOL isCustomResolution(CGSize res) {
[self.touchModeSelector addTarget:self action:@selector(touchModeChanged) forControlEvents:UIControlEventValueChanged];
[self touchModeChanged];
-
+
// init CustomOSC stuff
/* sets a reference to the correct 'LayoutOnScreenControlsViewController' depending on whether the user is on an iPhone or iPad */
self.layoutOnScreenControlsVC = [[LayoutOnScreenControlsViewController alloc] init];
@@ -620,16 +604,16 @@ BOOL isCustomResolution(CGSize res) {
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:[LocalizationHelper localizedStringForKey:@"Rebase in Stream View"]
message:[LocalizationHelper localizedStringForKey:@"Tap %d fingers to change layout in stream view, or change the number of fingers required to:", self->oscLayoutFingers]
preferredStyle:UIAlertControllerStyleAlert];
-
+
[alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) {
textField.placeholder = [LocalizationHelper localizedStringForKey:@"%d", self->oscLayoutFingers];
textField.keyboardType = UIKeyboardTypeNumberPad;
}];
-
+
UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:[LocalizationHelper localizedStringForKey:@"Cancel"]
style:UIAlertActionStyleCancel
handler:nil];
-
+
UIAlertAction *okAction = [UIAlertAction actionWithTitle:[LocalizationHelper localizedStringForKey:@"OK"]
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *action) {
@@ -642,16 +626,16 @@ BOOL isCustomResolution(CGSize res) {
} else {
NSLog(@"OK button tapped with no change");
}
-
+
// Continue execution after the alert is dismissed
if (!self->_mainFrameViewController.settingsExpandedInStreamView) {
[self invokeOscLayout]; // Don't open osc layout tool immediately during streaming
}
-
+
[self.onscreenControllerLabel setText:[LocalizationHelper localizedStringForKey: @"Tap %d Fingers to Change OSC Layout in Stream View", self->oscLayoutFingers]]; //update the osc label
[self keyboardToggleFingerNumSliderMoved]; //update keyboard toggle number;
}];
-
+
[alertController addAction:cancelAction];
[alertController addAction:okAction];
[self presentViewController:alertController animated:YES completion:nil];
@@ -666,7 +650,7 @@ BOOL isCustomResolution(CGSize res) {
- (void)onscreenControlChanged{
-
+
BOOL isIPhone = ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPhone);
if (isIPhone) {
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"iPhone" bundle:nil];
@@ -677,7 +661,7 @@ BOOL isCustomResolution(CGSize res) {
self.layoutOnScreenControlsVC = [storyboard instantiateViewControllerWithIdentifier:@"LayoutOnScreenControlsViewController"];
self.layoutOnScreenControlsVC.modalPresentationStyle = UIModalPresentationFullScreen;
}
-
+
bool customOscEnabled = [self isOnScreenControllerOrButtonEnabled] && [self.onscreenControlSelector selectedSegmentIndex] == OnScreenControlsLevelCustom;
// [self widget:self.keyboardToggleFingerNumSlider setEnabled:!customOscEnabled];
[self widget:self.oscTapExlusionAreaSizeSlider setEnabled:customOscEnabled];
@@ -764,10 +748,10 @@ BOOL isCustomResolution(CGSize res) {
bool oscSelectorEnabled = [self.touchModeSelector selectedSegmentIndex] == RELATIVE_TOUCH || [self.touchModeSelector selectedSegmentIndex] == REGULAR_NATIVE_TOUCH || [self.touchModeSelector selectedSegmentIndex] == ABSOLUTE_TOUCH;
bool customOscEnabled = [self isOnScreenControllerOrButtonEnabled] && [self.onscreenControlSelector selectedSegmentIndex] == OnScreenControlsLevelCustom;
bool isNativeTouch = [self.touchModeSelector selectedSegmentIndex] == PURE_NATIVE_TOUCH || [self.touchModeSelector selectedSegmentIndex] == REGULAR_NATIVE_TOUCH;
-
+
[self.onscreenControlSelector setEnabled:oscSelectorEnabled];
[self.largerStickLR1Selector setEnabled:oscSelectorEnabled]; // this selector stay aligned with oscSelector
-
+
[self widget:self.pointerVelocityModeDividerSlider setEnabled:isNativeTouch]; // pointer velocity scaling works only in native touch mode.
[self widget:self.touchPointerVelocityFactorSlider setEnabled:isNativeTouch]; // pointer velocity scaling works only in native touch mode.
[self widget:self.mousePointerVelocityFactorSlider setEnabled:[self.touchModeSelector selectedSegmentIndex] == RELATIVE_TOUCH]; // mouse velocity scaling works only in relative touch mode.
@@ -845,7 +829,7 @@ BOOL isCustomResolution(CGSize res) {
}
defaultBitrate = round(resolutionFactor * frameRateFactor) * 1000;
- _bitrate = MIN(defaultBitrate, 150000);
+ _bitrate = MIN(defaultBitrate, 100000);
[self.bitrateSlider setValue:[self getSliderValueForBitrate:_bitrate] animated:YES];
[self updateBitrateText];
@@ -983,14 +967,14 @@ BOOL isCustomResolution(CGSize res) {
- (void) keyboardToggleFingerNumSliderMoved{
// bool oscEnabled = [self isOnScreenControllerOrButtonEnabled];
bool customOscEnabled = [self isOnScreenControllerOrButtonEnabled] && [self.onscreenControlSelector selectedSegmentIndex] == OnScreenControlsLevelCustom;
-
+
CGFloat sliderValue = self.keyboardToggleFingerNumSlider.value;
if(customOscEnabled){
// exclude self->oscLayoutFingers when custom osc is enabled
if(sliderValue > self->oscLayoutFingers - 1 && sliderValue < self->oscLayoutFingers) [self.keyboardToggleFingerNumSlider setValue: self->oscLayoutFingers - 1];
if(sliderValue >= self->oscLayoutFingers && sliderValue < self->oscLayoutFingers + 1) [self.keyboardToggleFingerNumSlider setValue: self->oscLayoutFingers + 1];
}
-
+
sliderValue = self.keyboardToggleFingerNumSlider.value;
if(sliderValue > 10.5f) [self.keyboardToggleFingerNumLabel setText:[LocalizationHelper localizedStringForKey:@"Local Keyboard Toggle Disabled"]];
else [self.keyboardToggleFingerNumLabel setText:[LocalizationHelper localizedStringForKey:@"To Toggle Local Keyboard: Tap %d Fingers", (uint16_t)sliderValue]]; // Initiate label display, exclude 5 fingers.
@@ -1019,8 +1003,6 @@ BOOL isCustomResolution(CGSize res) {
case 1:
return 60;
case 2:
- return 90;
- case 3:
return 120;
default:
abort();
@@ -1069,30 +1051,6 @@ BOOL isCustomResolution(CGSize res) {
return resolutionTable[[self.resolutionSelector selectedSegmentIndex]].width;
}
-- (void) frameQueueSizeSliderMoved {
- assert(self.frameQueueSizeSlider.value >= 0 && self.frameQueueSizeSlider.value <= 5);
- _frameQueueSize = (int)self.frameQueueSizeSlider.value;
- [self updateFrameQueueSizeText];
-}
-
-- (void) updateFrameQueueSizeText {
- [self.frameQueueSizeLabel setText:[NSString stringWithFormat:@"Frames to buffer: %ld", _frameQueueSize ]];
-}
-
-- (void) enableGraphsChanged {
- [self.graphOpacityStepper setEnabled:[self.enableGraphsSelector selectedSegmentIndex] == 1 ? YES : NO];
-}
-
-- (void) graphOpacityStepperMoved {
- assert(self.graphOpacityStepper.value >= 0 && self.graphOpacityStepper.value <= 100);
- _graphOpacity = (int)self.graphOpacityStepper.value;
- [self updateGraphOpacityText];
-}
-
-- (void) updateGraphOpacityText {
- [self.enableGraphsLabel setText:[NSString stringWithFormat:@"Performance Graphs - Opacity: %ld%%", _graphOpacity ]];
-}
-
- (void) saveSettings {
DataManager* dataMan = [[DataManager alloc] init];
NSInteger framerate = [self getChosenFrameRate];
@@ -1116,7 +1074,6 @@ BOOL isCustomResolution(CGSize res) {
BOOL largerStickLR1 = [self.largerStickLR1Selector selectedSegmentIndex] == 1;
BOOL liftStreamViewForKeyboard = [self.liftStreamViewForKeyboardSelector selectedSegmentIndex] == 1;
BOOL showKeyboardToolbar = [self.showKeyboardToolbarSelector selectedSegmentIndex] == 1;
- NSInteger renderingBackend = [self.renderingBackendSelector selectedSegmentIndex];
BOOL optimizeGames = [self.optimizeSettingsSelector selectedSegmentIndex] == 1;
BOOL multiController = [self.multiControllerSelector selectedSegmentIndex] == 1;
BOOL swapABXYButtons = [self.swapABXYButtonsSelector selectedSegmentIndex] == 1;
@@ -1130,7 +1087,6 @@ BOOL isCustomResolution(CGSize res) {
NSInteger touchMode = [self.touchModeSelector selectedSegmentIndex];
NSInteger statsOverlayLevel = [self.statsOverlaySelector selectedSegmentIndex];
BOOL statsOverlayEnabled = statsOverlayLevel != 0;
- BOOL enableGraphs = [self.enableGraphsSelector selectedSegmentIndex] == 1;
BOOL enableHdr = [self.hdrSelector selectedSegmentIndex] == 1;
BOOL unlockDisplayOrientation = [self.unlockDisplayOrientationSelector selectedSegmentIndex] == 1;
NSInteger resolutionSelected = [self.resolutionSelector selectedSegmentIndex];
@@ -1164,19 +1120,16 @@ BOOL isCustomResolution(CGSize res) {
enableYUV444:enableYUV444
enablePIP:enablePIP
useFramePacing:useFramePacing
- frameQueueSize:_frameQueueSize
enableHdr:enableHdr
btMouseSupport:btMouseSupport
// absoluteTouchMode:absoluteTouchMode
touchMode:touchMode
statsOverlayLevel:statsOverlayLevel
+ statsOverlayEnabled:statsOverlayEnabled
unlockDisplayOrientation:unlockDisplayOrientation
resolutionSelected:resolutionSelected
externalDisplayMode:externalDisplayMode
localMousePointerMode:localMousePointerMode];
- enableGraphs:enableGraphs
- graphOpacity:_graphOpacity
- renderingBackend:renderingBackend];
}
- (void)didReceiveMemoryWarning {
diff --git a/Moonlight TV/Info.plist b/Moonlight TV/Info.plist
index 863b72cd..dbf17f9a 100644
--- a/Moonlight TV/Info.plist
+++ b/Moonlight TV/Info.plist
@@ -48,8 +48,6 @@
NSLocalNetworkUsageDescription
Moonlight uses the local network to connect to your gaming PC for streaming.
- UIDesignRequiresCompatibility
-
UIMainStoryboardFile
Main
UIRequiredDeviceCapabilities
diff --git a/Moonlight TV/Settings.bundle/Root.plist b/Moonlight TV/Settings.bundle/Root.plist
index 6b0efbc3..80881885 100644
--- a/Moonlight TV/Settings.bundle/Root.plist
+++ b/Moonlight TV/Settings.bundle/Root.plist
@@ -36,44 +36,6 @@
4K
-
- Type
- PSToggleSwitchSpecifier
- Title
- Request HDR Video
- Key
- enableHdr
- DefaultValue
-
-
-
- Type
- PSMultiValueSpecifier
- Title
- Audio Configuration
- Key
- audioConfig
- DefaultValue
- 2
- Titles
-
- Stereo
- LPCM 5.1
- LPCM 7.1
-
- Values
-
- 2
- 6
- 8
-
-
-
- Type
- PSGroupSpecifier
- Title
- Enable Match Content Rate for best results
-
Type
PSMultiValueSpecifier
@@ -85,16 +47,12 @@
60
Values
- 24
30
- 59.94
60
Titles
- 24 FPS
30 FPS
- 59.94 FPS (experimental)
60 FPS
@@ -138,6 +96,28 @@
150000
+
+ Type
+ PSMultiValueSpecifier
+ Title
+ Audio Configuration
+ Key
+ audioConfig
+ DefaultValue
+ 2
+ Titles
+
+ Stereo
+ LPCM 5.1
+ LPCM 7.1
+
+ Values
+
+ 2
+ 6
+ 8
+
+
Type
PSGroupSpecifier
@@ -232,53 +212,37 @@
Type
PSMultiValueSpecifier
Title
- Frames to Buffer
+ Frame Pacing Preference
Key
- frameQueueSize
- DefaultValue
- 2
- Values
-
- 1
- 2
- 3
- 4
- 5
-
- Titles
-
- 1 - Lowest Latency
- 2 - Recommended
- 3 - Smoother Video
- 4
- 5
-
-
-
- Type
- PSMultiValueSpecifier
- Title
- Rendering Backend
- Key
- renderingBackend
+ useFramePacing
DefaultValue
0
Values
- 0
- 1
+ 0
+ 1
Titles
- Metal
- AVSampleBuffer
+ Lowest Latency
+ Smoothest Video
+
+ Type
+ PSToggleSwitchSpecifier
+ Title
+ Request HDR Video
+ Key
+ enableHdr
+ DefaultValue
+
+
Type
PSGroupSpecifier
Title
- Double-click select button for stats
+ Troubleshooting
Type
@@ -290,42 +254,6 @@
DefaultValue
-
- Type
- PSToggleSwitchSpecifier
- Title
- Performance Graphs
- Key
- enableGraphs
- DefaultValue
-
-
-
- Type
- PSMultiValueSpecifier
- Title
- Performance Graph Opacity
- Key
- graphOpacity
- DefaultValue
- 50
- Values
-
- 0
- 25
- 50
- 75
- 100
-
- Titles
-
- 0% - Transparent
- 25%
- 50%
- 75%
- 100% - Opaque
-
-
diff --git a/MoonlightTests/FrameQueueTests.m b/MoonlightTests/FrameQueueTests.m
deleted file mode 100644
index b8870aee..00000000
--- a/MoonlightTests/FrameQueueTests.m
+++ /dev/null
@@ -1,271 +0,0 @@
-
-#import
-#import
-
-#define FRAME_QUEUE_VERBOSE
-
-#import "FrameQueue.h"
-#import "Logger.h"
-#include "Limelight.h"
-
-@interface FrameQueueTests : XCTestCase
-@property (nonatomic, strong) FrameQueue *queue;
-@end
-
-@implementation FrameQueueTests {
- int _frameNumber;
-}
-
-- (void)setUp {
- [super setUp];
- self.queue = [FrameQueue sharedInstance];
- [self.queue setHighWaterMark:3];
- _frameNumber = 0;
-}
-
-- (void)tearDown {
- [self.queue clear];
- self.queue = nil;
- [super tearDown];
-}
-
-- (CMSampleBufferRef)makeEmptySampleBufferFor:(uint32_t)tick {
- CMSampleTimingInfo sampleTiming = {
- .duration = kCMTimeInvalid,
- .presentationTimeStamp = CMTimeMake((int64_t)tick, 90000),
- .decodeTimeStamp = kCMTimeInvalid,
- };
-
- CMSampleBufferRef sampleBuffer = NULL;
- OSStatus status = CMSampleBufferCreate(kCFAllocatorDefault,
- NULL, NO, NULL, NULL, NULL,
- 1, 1, &sampleTiming, 0, NULL,
- &sampleBuffer);
- XCTAssertEqual(status, noErr, @"makeEmptySampleBufferFor:%d ok", tick);
- return sampleBuffer;
-}
-
-- (Frame *)makeFrameNumber:(int)num type:(int)type pts:(uint32_t)tick {
- CMSampleBufferRef buf = [self makeEmptySampleBufferFor:tick];
- CFRetain(buf); // emulate retain that happens in CMSampleBufferCreateReadyWithImageBuffer
- Frame *frame = [[Frame alloc] initWithSampleBuffer:buf
- frameNumber:num
- frameType:type];
- _frameNumber = num; // reset the counter
- return frame;
-}
-
-- (Frame *)makeFrame {
- const uint32_t ONE_FRAME = 90000 / 60;
- static uint32_t tick = 0;
- Frame *frame = [self makeFrameNumber:++_frameNumber
- type:FRAME_TYPE_PFRAME
- pts:tick];
- tick += ONE_FRAME;
- return frame;
-}
-
-- (void)testEnqueueThenDequeue {
- Frame *f1 = [self makeFrameNumber:1 type:FRAME_TYPE_IDR pts:0];
- Frame *f2 = [self makeFrame];
-
- [self.queue enqueue:f1];
- [self.queue enqueue:f2];
- XCTAssertEqual([self.queue count], 2);
-
- Frame *out1 = [self.queue dequeue];
- XCTAssertEqual(out1.frameNumber, 1);
- XCTAssertEqual([self.queue count], 1);
-
- Frame *out2 = [self.queue dequeue];
- XCTAssertEqual(out2.frameNumber, 2);
- XCTAssertEqual([self.queue count], 0);
-
- // empty now
- XCTAssertNil([self.queue dequeue]);
-}
-
-- (void)testOverflow {
- [self.queue setHighWaterMark:3];
-
- for (int i = 0; i < self.queue.highWaterMark; i++) {
- [self.queue enqueue:[self makeFrame]];
- }
- XCTAssertEqual([self.queue count], 3);
-
- // queue should be full at 3
- // 4: dropped, 5: queued, causes 1 to be dropped
- // 6: dropped, 7: queued, causes 2 to be dropped
- // 8: dropped, 9: queued, causes 3 to be dropped
- for (int i = 0; i < 6; i++) {
- [self.queue enqueue:[self makeFrame]];
- }
- XCTAssertEqual([self.queue count], 3);
- XCTAssertEqual([[self.queue frameDropMetrics] total], 6);
- Log(LOG_I, @"queue after overflow: %@", self.queue);
- for (NSNumber *expected in @[@5, @7, @9]) {
- Frame *frame = [self.queue dequeue];
- XCTAssertEqual(frame.frameNumber, expected.integerValue);
- }
-}
-
-- (void)testOverflowWithIDR {
- [self.queue setHighWaterMark:5];
-
- [self.queue enqueue:[self makeFrameNumber:1 type:FRAME_TYPE_IDR pts:0]];
- [self.queue enqueue:[self makeFrame]];
- [self.queue enqueue:[self makeFrameNumber:3 type:FRAME_TYPE_IDR pts:0]];
- [self.queue enqueue:[self makeFrame]];
- [self.queue enqueue:[self makeFrame]];
- XCTAssertEqual([self.queue count], 5);
-
- // queue should be full at 5
- // 6: dropped, 7: queued, does NOT drop 1 (IDR)
- // 8: dropped, 9: queued, does NOT drop 1 (IDR) - will be stuck here until it's dequeued
- // 10: dropped, 11: queued, does NOT drop 3 (IDR)
- for (int i = 0; i < 6; i++) {
- [self.queue enqueue:[self makeFrame]];
- }
- XCTAssertEqual([self.queue count], 8);
- XCTAssertEqual([[self.queue frameDropMetrics] total], 3);
- Log(LOG_I, @"queue after overflow: %@", self.queue);
-
- for (NSNumber *expected in @[@1, @2, @3, @4, @5, @7, @9, @11]) {
- XCTAssertEqual([self.queue dequeue].frameNumber, expected.integerValue);
- }
-}
-
-- (void)testClear {
- for (int i = 0; i < self.queue.highWaterMark; i++) {
- [self.queue enqueue:[self makeFrame]];
- }
- XCTAssertEqual([self.queue count], self.queue.highWaterMark);
-
- [self.queue clear];
- XCTAssertEqual([self.queue count], 0);
- XCTAssertEqual([[self.queue frameDropMetrics] total], 0);
- XCTAssertNil([self.queue dequeue]);
-}
-
-- (void)testDequeueWithTimeout {
- [self.queue enqueue:[self makeFrame]];
-
- // should return immediately with an item in the queue
- CFTimeInterval t0 = CACurrentMediaTime();
- Frame *out = [self.queue dequeueWithTimeout:1];
- XCTAssertNotNil(out);
- XCTAssertEqual(out.frameNumber, 1);
- XCTAssertLessThan(CACurrentMediaTime() - t0, 0.9);
-
- // should return nil after waiting
- t0 = CACurrentMediaTime();
- XCTAssertNil([self.queue dequeueWithTimeout:0.2]);
- XCTAssertGreaterThan(CACurrentMediaTime() - t0, 0.2);
-
- // no timeout
- t0 = CACurrentMediaTime();
- XCTAssertNil([self.queue dequeueWithTimeout:0]);
- XCTAssertLessThan(CACurrentMediaTime() - t0, 0.1);
-}
-
-- (void)testDuration {
- // 2nd frame will compute 1st frame's duration. makeFrame generates sequential frames at 60fps
- [self.queue clear];
- [self.queue enqueue:[self makeFrame]];
- [self.queue enqueue:[self makeFrame]];
- XCTAssertEqual([self.queue count], 2);
- Frame *first = [self.queue dequeue];
- XCTAssertEqualWithAccuracy(first.duration, (float)1.0f / 60, 0.0001f);
-}
-
-- (void)testEstimatedFramerate {
- // prime the start point
- [self.queue estimatedFramerate];
- for (int i = 0; i < 24 * 5; i++) {
- // these will all get dropped but they are still counted for fps
- [self.queue enqueue:[self makeFrame]];
- [NSThread sleepForTimeInterval:1.0f / 24];
- }
- XCTAssertEqualWithAccuracy([self.queue estimatedFramerate], 24.0f, 4.0f);
-}
-
-- (void)testProducerConsumer {
- static const int TotalFrames = 500;
-
- // We expect exactly kTotalFrames dequeues.
- XCTestExpectation *dequeueAll = [self expectationWithDescription:@"consumer dequeued all frames"];
- dequeueAll.expectedFulfillmentCount = TotalFrames;
-
- // Producer
- dispatch_queue_t producerQ = dispatch_queue_create("moonlight.test.producer", DISPATCH_QUEUE_CONCURRENT);
- dispatch_async(producerQ, ^{
- for (int i = 0; i < TotalFrames; i++) {
- [self.queue enqueue:[self makeFrame]];
- [NSThread sleepForTimeInterval:0.002];
- }
- });
-
- // Consumer
- dispatch_queue_t consumerQ = dispatch_queue_create("moonlight.test.consumer", DISPATCH_QUEUE_CONCURRENT);
- dispatch_async(consumerQ, ^{
- for (int expected = 0; expected < TotalFrames; expected++) {
- Frame *frame = [self.queue dequeueWithTimeout:1.0];
- XCTAssertNotNil(frame, @"Expected frame %d, but got nil", expected);
- XCTAssertEqual(frame.frameNumber, expected + 1, @"Expected frameNumber %d but got %d", expected + 1, frame.frameNumber);
- [dequeueAll fulfill];
- }
- });
-
- // Wait up to 10s for all frames to be consumed
- [self waitForExpectationsWithTimeout:10.0 handler:^(NSError * _Nullable error) {
- if (error) {
- XCTFail(@"Timed out waiting for consumer: %@", error);
- }
- XCTAssertEqual([self.queue count], 0, @"Queue should be empty at end");
- }];
-}
-
-- (void)testPerformance {
- XCTMeasureOptions *options = [[XCTMeasureOptions alloc] init];
- options.iterationCount = 20;
- if (@available(iOS 13.0, *)) {
- static const int TotalFrames = 1000;
- dispatch_queue_t producerQ = dispatch_queue_create("moonlight.test.producer", DISPATCH_QUEUE_CONCURRENT);
- dispatch_queue_t consumerQ = dispatch_queue_create("moonlight.test.consumer", DISPATCH_QUEUE_CONCURRENT);
-
- [self measureWithOptions:options block:^{
- XCTestExpectation *dequeueAll = [self expectationWithDescription:@"consumer dequeued all frames"];
- dequeueAll.expectedFulfillmentCount = TotalFrames;
-
- [self.queue setHighWaterMark:5];
-
- // Producer
- dispatch_async(producerQ, ^{
- for (int i = 0; i < TotalFrames; i++) {
- //[self.queue enqueue:[self makeFrame]];
- [self.queue enqueue:[self makeFrame] withSlackSize:3];
- [NSThread sleepForTimeInterval:0.001];
- }
- });
-
- // Consumer
- dispatch_async(consumerQ, ^{
- for (int expected = 0; expected < TotalFrames; expected++) {
- [self.queue dequeueWithTimeout:1.0f];
- [dequeueAll fulfill];
- }
- });
-
- // Wait up to 10s for all frames to be consumed
- [self waitForExpectationsWithTimeout:100.0 handler:^(NSError * _Nullable error) {
- if (error) {
- XCTFail(@"Timed out waiting for consumer: %@", error);
-
- [self.queue clear];
- }
- }];
- }];
- }
-}
-
-@end
diff --git a/VoidLink/AppDelegate.m b/VoidLink/AppDelegate.m
index bc9712b2..e1a163ff 100644
--- a/VoidLink/AppDelegate.m
+++ b/VoidLink/AppDelegate.m
@@ -95,32 +95,27 @@ static NSString* DB_NAME = @"Limelight_iOS.sqlite";
{
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
- Log(LOG_I, @"TODO: applicationWillResignActive");
}
- (void)applicationDidEnterBackground:(UIApplication *)application
{
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
- Log(LOG_I, @"TODO: applicationDidEnterBackground");
}
- (void)applicationWillEnterForeground:(UIApplication *)application
{
// Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
- Log(LOG_I, @"TODO: applicationWillEnterForeground");
}
- (void)applicationDidBecomeActive:(UIApplication *)application
{
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
- Log(LOG_I, @"TODO: applicationDidBecomeActive");
}
- (void)applicationWillTerminate:(UIApplication *)application
{
// Saves changes in the application's managed object context before the application terminates.
- Log(LOG_I, @"applicationWillTerminate, calling saveContext");
[self saveContext];
}
diff --git a/VoidLink/Input/AbsoluteTouchHandler.m b/VoidLink/Input/AbsoluteTouchHandler.m
index 4e51f147..2875bf55 100644
--- a/VoidLink/Input/AbsoluteTouchHandler.m
+++ b/VoidLink/Input/AbsoluteTouchHandler.m
@@ -75,7 +75,7 @@
if ([[event allTouches] count] > 1) {
return;
}
-
+
UITouch* touch = [touches anyObject];
CGPoint touchLocation = [touch locationInView:streamView];
diff --git a/VoidLink/Input/PureNativeTouchHandler.m b/VoidLink/Input/PureNativeTouchHandler.m
index d1e9df14..988d8870 100644
--- a/VoidLink/Input/PureNativeTouchHandler.m
+++ b/VoidLink/Input/PureNativeTouchHandler.m
@@ -59,7 +59,7 @@
}
self->activePointerIds = [NSMutableSet set];
self->blacklistedTouches = [NSMutableSet set];
-
+
self->asyncNativeTouch = settings.asyncNativeTouchPriority.intValue != AsyncNativeTouchOff;
switch(settings.asyncNativeTouchPriority.intValue){
@@ -239,6 +239,7 @@
});
else {
for (UITouch* touch in touches){
+ // continue to the next loop if current touch is already captured by OSC. works only in regular native touch
if(self->activateCoordSelector) [self updatePointerObjInDict:touch];
[self sendTouchEvent:touch withTouchtype:LI_TOUCH_EVENT_MOVE];
[[self getPointerObjFromDict:touch] doesNeedResetCoords]; // execute the judging of doesReachBoundary for current pointer instance. (happens after the event is sent to Sunshine service)
diff --git a/VoidLink/Metal/MetalVideoRenderer.m b/VoidLink/Metal/MetalVideoRenderer.m
index 43b3e82a..93078fb9 100644
--- a/VoidLink/Metal/MetalVideoRenderer.m
+++ b/VoidLink/Metal/MetalVideoRenderer.m
@@ -580,9 +580,6 @@ CFStringRef __currentColorSpace;
fragmentShaderName = is10BitInput ? @"ps_draw_triplanar_10bit" : @"ps_draw_triplanar_8bit";
}
- Log(LOG_I, @"DEBUG: CVPixelBuffer format: 0x%X, planes: %zu, is10BitInput: %d, shader: %@, framebuffer format: %lu",
- pixelFormatType, planes, is10BitInput, fragmentShaderName, (unsigned long)framebufferPixelFormat);
-
pipelineDesc.colorAttachments[0].pixelFormat = framebufferPixelFormat;
pipelineDesc.vertexBuffers[0].mutability = MTLMutabilityImmutable;
@@ -638,10 +635,6 @@ CFStringRef __currentColorSpace;
return;
} else {
id texture = CVMetalTextureGetTexture(_cvMetalTextures[0]);
- Log(LOG_I, @"DEBUG: Created BGRA texture: format=%lu, width=%zu, height=%zu",
- (unsigned long)MTLPixelFormatBGRA8Unorm,
- CVPixelBufferGetWidth(frame.pixelBuffer),
- CVPixelBufferGetHeight(frame.pixelBuffer));
}
} else {
// Handle planar YUV formats
@@ -683,10 +676,6 @@ CFStringRef __currentColorSpace;
return;
} else {
id texture = CVMetalTextureGetTexture(_cvMetalTextures[i]);
- Log(LOG_I, @"DEBUG: Created texture for plane %zu: format=%lu, width=%zu, height=%zu",
- i, (unsigned long)fmt,
- CVPixelBufferGetWidthOfPlane(frame.pixelBuffer, i),
- CVPixelBufferGetHeightOfPlane(frame.pixelBuffer, i));
}
}
}
diff --git a/VoidLink/Metal/MetalView.h b/VoidLink/Metal/MetalView.h
index 676973e9..4564f51d 100644
--- a/VoidLink/Metal/MetalView.h
+++ b/VoidLink/Metal/MetalView.h
@@ -7,7 +7,6 @@
//
#import
-#import
#import
#import
#import "MetalConfig.h"
diff --git a/VoidLink/UIComputerView.m b/VoidLink/UIComputerView.m
deleted file mode 100644
index e69de29b..00000000
diff --git a/ideas/pts-display-link.txt b/ideas/pts-display-link.txt
deleted file mode 100644
index 69c12906..00000000
--- a/ideas/pts-display-link.txt
+++ /dev/null
@@ -1,88 +0,0 @@
-#pragma mark DisplayLink - RTP PTS timestamp-based frame pacing (experimental)
-
-// TODO: handle presentationTimeUs rollover every 13 hours
-
-// This frame pacing method attempts to use timestamps from Sunshine to determine when
-// frames should be displayed. Sunshine v2025.600+ required.
-- (void)framePacingUsingTimestamps:(CADisplayLink *)link {
- // All times are in seconds
- static CFTimeInterval anchorLocal = 0.0f;
- static CMTime anchorHost;
- static CFTimeInterval lastTargetLocal = 0.0f;
-
- CFTimeInterval start = link.timestamp;
- CFTimeInterval deadline = link.targetTimestamp;
- _displayRefreshRate = 1.0f / (deadline - start);
-
- [self checkDisplayLayer];
-
- Frame *frame = nil;
-
- CFTimeInterval dl0 = CACurrentMediaTime();
- static CFTimeInterval avgOverhead = 0.004f; // averaged each callback
- CFTimeInterval waitFor = deadline - dl0 - avgOverhead;
- if (waitFor < 0.001f) {
- waitFor = 0.001f;
- }
-
- // Process the next frame for display
- frame = [_frameQueue dequeueWithTimeout:waitFor];
- if (frame) {
- CFTimeInterval now = CACurrentMediaTime();
-
- // during stream startup, wait and let the first frames through untouched
- if (!_seenFrameOne) {
- Log(LOG_I, @"Frame pacing (pts mode): target %f Hz with %d FPS stream", _displayRefreshRate, self->frameRate);
- _seenFrameOne = YES;
-
- // Just let frame 1 through, we don't want to anchor to it
- Log(LOG_I, @"[%.3f] rendering frame %d / %.3f, queue size %d",
- deadline, frame.frameNumber, frame.pts, [_frameQueue count]);
- [self renderFrame:frame atTime:frame.pts90];
- return;
- }
-
- if (!CMTIME_IS_VALID(anchorHost)) {
- // Use this frame as the anchor point. This is normally frame 2.
- anchorHost = frame.pts90;
- anchorLocal = now;
- Log(LOG_I, @"Setting anchor point: anchorHost=%.3f == anchorLocal=%.3f",
- CMTimeGetSeconds(anchorHost), anchorLocal);
- }
-
- //CMTime targetTime = CMTimeSubtract(frame.pts90, frameQueue.ptsCorrection); // total of all dropped frames
- CMTime targetTime = frame.pts90;
- CFTimeInterval targetLocal = CMTimeGetSeconds(targetTime);
-
-#ifdef DISPLAYLINK_VERBOSE
- // iOS double-buffering renders the frame we queue here during the interval +2 from now
- CMTime hostDelta = CMTimeSubtract(frame.pts90, anchorHost);
- CFTimeInterval hostDeltaF = CMTimeGetSeconds(hostDelta);
- CMTime drift = CMTimeSubtract(hostDelta, CMTimeMakeWithSeconds(now - anchorLocal, NSEC_PER_SEC));
- Log(LOG_I, @"[%.3f] rendering frame %d / %.3f @ %.3f, drift %.3f, queue size %d",
- deadline - anchorLocal, frame.frameNumber, hostDeltaF, targetLocal,
- CMTimeGetSeconds(drift), [_frameQueue count]);
-#endif
-
- // Schedule the frame at its original timestamp, adjusted by a correction factor (total duration of all dropped frames)
- [self renderFrame:frame atTime:targetTime];
-
- // Update metrics
- if (lastTargetLocal != 0) {
- [self->_callbacks observeFloat:PLOT_FRAMETIME value:(targetLocal - lastTargetLocal) * 1000.0];
- }
- lastTargetLocal = targetLocal;
-
- // weighted moving average of how much time displayLink needs after dequeuing a frame.
- // This is used to avoid overshooting a vsync by waiting too long.
- const double alpha = 0.1f;
- avgOverhead = ((CACurrentMediaTime() - dl0) * alpha) + (avgOverhead * (1.0 - alpha));
- }
-
- // are we returning too late?
- CFTimeInterval dl2 = CACurrentMediaTime();
- if (dl2 > deadline) {
- Log(LOG_W, @"*** returned past deadline by %.3f ms (%f > %f)", (dl2 - deadline) * 1000.0, dl2, deadline);
- }
-}
-
diff --git a/moonlight-common/moonlight-common.xcodeproj/project.pbxproj b/moonlight-common/moonlight-common.xcodeproj/project.pbxproj
index 89894ed5..8eed6eb0 100644
--- a/moonlight-common/moonlight-common.xcodeproj/project.pbxproj
+++ b/moonlight-common/moonlight-common.xcodeproj/project.pbxproj
@@ -374,7 +374,7 @@
FB290E2619B37A4E004C83CF /* Project object */ = {
isa = PBXProject;
attributes = {
- BuildIndependentTargetsInParallel = NO;
+ BuildIndependentTargetsInParallel = YES;
LastUpgradeCheck = 1510;
ORGANIZATIONNAME = "Moonlight Stream";
TargetAttributes = {
diff --git a/scripts/SettingsHelper.pl b/scripts/SettingsHelper.pl
deleted file mode 100755
index 9231155b..00000000
--- a/scripts/SettingsHelper.pl
+++ /dev/null
@@ -1,268 +0,0 @@
-#!/usr/bin/env perl
-#
-# Don't try to follow the logic in here, XML::Twig allows for some real nonsense
-
-use strict;
-use warnings;
-
-use Getopt::Long qw(GetOptions);
-use Pod::Usage qw(pod2usage);
-use XML::Twig;
-
-my $inSettings = 0;
-my @list = ();
-my @clipboard = ();
-my $help = 0;
-my $after;
-my $move;
-my $to;
-
-GetOptions(
- 'help|?' => \$help,
- 'add-after=s' => \$after,
- 'move=s' => \$move,
- 'to=s' => \$to,
-) or pod2usage(2);
-pod2usage(1) if $help || ($move && !$to) || (!$move && $to);
-
-my $xml = shift || pod2usage(2);
-
-my $twig = XML::Twig->new(
- twig_handlers => {
- '#COMMENT' => sub {
- my ($t, $comment) = @_;
- if ($comment->{comment} eq 'Settings View Controller') {
- $inSettings = 1;
- }
- else {
- $inSettings = 0;
- }
- },
- label => sub {
- my ($t, $label) = @_;
- if ($inSettings) {
- push @list, [ label => $label ];
- }
- },
- segmentedControl => sub {
- my ($t, $control) = @_;
- if ($inSettings) {
- push @list, [ control => $control ];
- }
- },
- slider => sub {
- my ($t, $slider) = @_;
- if ($inSettings) {
- push @list, [ slider => $slider ];
- }
- },
- view => sub {
- my ($t, $view) = @_;
- if ($inSettings) {
- push @list, [ view => $view ];
- }
- },
- },
- comments => 'process',
- keep_atts_order => 1,
- pretty_print => 'indented',
- empty_tags => 'normal',
-);
-
-$twig->parsefile($xml);
-
-if ($after) {
- do_after($twig, $after);
-}
-elsif ($move && $to) {
- do_move($twig, $move, $to);
-}
-
-do_output($twig);
-
-sub do_after {
- my ($twig, $after) = @_;
-
- my $LABEL_TEMPLATE = qq{
- };
-
- my $CONTROL_TEMPLATE = qq{
-
-
-
-
-
-
-
-
-
- };
-
- my @to_add = (
- [ label => XML::Twig::Elt->parse( sprintf($LABEL_TEMPLATE, generate_id()) ) ],
- [ control => XML::Twig::Elt->parse( sprintf($CONTROL_TEMPLATE, generate_id()) ) ],
- );
-
- # Find where to insert them
- for (my $i = 0; $i < scalar @list; $i++) {
- my ($type, $e) = @{$list[$i]};
- if ($type eq 'label' && ($e->att('text') // q{}) eq $after) {
- ($type, $e) = @{$list[$i + 1]};
-
- # paste the block to a new location
- for my $tm (@to_add) {
- $tm->[1]->paste(after => $e);
- $e = $tm->[1];
- }
-
- # also move the block in the ordering list, which is where the y values are adjusted
- $i += 2;
- splice(@list, $i, 0, @to_add);
- }
- }
-}
-
-sub do_move {
- my ($twig, $move, $to) = @_;
-
- my @to_move = ();
-
- # Pull out the bits to move
- for (my $i = 0; $i < scalar @list; $i++) {
- my ($type, $e) = @{$list[$i]};
- if ($type eq 'label' && ($e->att('text') // q{}) eq $move) {
- # move this item and the next one (the control)
- push @to_move, [ $type => $e->cut ];
- ($type, $e) = @{$list[$i + 1]};
- push @to_move, [ $type => $e->cut ];
- splice(@list, $i, 2);
- last;
- }
- }
-
- # Find where to insert them
- for (my $i = 0; $i < scalar @list; $i++) {
- my ($type, $e) = @{$list[$i]};
- if ($type eq 'label' && ($e->att('text') // q{}) eq $to) {
- ($type, $e) = @{$list[$i + 1]};
-
- # paste the block to a new location
- for my $tm (@to_move) {
- $tm->[1]->paste(after => $e);
- $e = $tm->[1];
- }
-
- # also move the block in the ordering list, which is where the y values are adjusted
- $i += 2;
- splice(@list, $i, 0, @to_move);
- }
- }
-}
-
-sub do_output {
- my $twig = shift;
-
- # first label
- my $x = 16;
- my $y = 20;
-
- # spacing
- my $label_to_control = 29;
- my $control_to_label = 35;
- my $label_to_label = 43;
- my $slider_to_next = 29;
- my $view_to_next = 43;
- my $seen_rdv = 0;
- my $last = q{};
-
- printf STDERR "%30s %s %s\n\n", q{}, "auto", "current";
-
- for my $row (@list) {
- my ($type, $e) = @{$row};
- my $rect = $e->has_child('rect');
- my $orig_y = $rect->att('y');
-
- $rect->set_att(x => $x);
- $rect->set_att(y => $y);
-
- if ($type eq "label") {
- my $text = $e->att('userLabel') // $e->att('text') // "(unknown label)";
- printf STDERR "%30s %d %4d\n", $text, $y, $orig_y;
-
- if ($text =~ /Caption/) { # space after captions is a bit larger
- $y += $label_to_label;
- }
- else {
- $y += $label_to_control;
- }
- }
- elsif ($type eq "control") {
- my ($userLabel) = $e->att('userLabel') // "(unknown control)";
- printf STDERR "%30s %d %4d\n", $userLabel, $y, $orig_y;
-
- $y += $control_to_label;
- }
- elsif ($type eq "slider") {
- printf STDERR "%30s %d %4d\n", "slider", $y, $orig_y;
- $y += $slider_to_next;
- }
- elsif ($type eq "view") {
- my $customClass = $e->att('customClass') // q{};
- if ($customClass && $customClass eq "UIScrollView") {
- # This is the entire view, so we're done
- last;
- }
-
- printf STDERR "%30s %d %4d\n", "view", $y, $orig_y;
- $y += $view_to_next;
- }
- $last = $type;
- }
-
- # Output the entire XML with our changes
- $twig->print;
-}
-
-sub generate_id {
- # generate an id of the form K4A-oH-Bs0
- my $chars = [ split //, 'abcdefghiklmnopqrstuvwxyzABCDEFGHIKLMNOPQRSTUVWXYZ0123456789' ];
-
- my $random_fragment = sub {
- my $length = shift;
- return '' if $length < 1;
- my $fragment = '';
- $fragment .= $chars->[int(rand(scalar @{$chars}))] for (1..$length);
- return $fragment;
- };
-
- return join '-', (
- $random_fragment->(3),
- $random_fragment->(2),
- $random_fragment->(3),
- );
-}
-
-=pod
-
-=head1 NAME
-
-SettingsHelper.pl - Less painful storyboard settings elements
-
-=head1 SYNOPSIS
-
-SettingsHelper.pl [options] [iPad.storyboard] > New.storyboard
-
-Run with no options to display the list of items in a storyboard.
-
- Options:
- --add-after LABEL Insert a new empty label + control pair after the given label (and its control)
- --move FROM --to TO Move the first label and control after the "to" label
- --help This help message.
-
-=cut