Compare commits

..
69 Commits
Author SHA1 Message Date
Diego Waxemberg ba81d1d6a9 Bumping version and creating build 1.0.2 2016-02-19 13:08:01 -08:00
Cameron Gutman 402ce4b3ec Update common-c to support Gen 5 servers 2016-02-19 15:09:37 -05:00
Cameron Gutman fc713453ad Merge branch 'master' of github.com:moonlight-stream/moonlight-ios 2016-02-16 10:38:25 -05:00
Cameron Gutman b222ccffbb Fix analysis warning 2016-02-16 10:37:59 -05:00
Michelle Bergeron 56ce7329d2 Point to website, add wiki link. 2016-02-15 22:39:32 -08:00
Michelle Bergeron 99ab6db557 Added note about Apple TV in effort to stave off some emails 2016-02-07 21:53:55 -08:00
Cameron Gutman d7adf96f3a Newer GFE versions don't mind resuming another device's stream 2016-02-06 23:10:36 -05:00
Cameron Gutman 0ae58c61a9 Remove apps from the database when they are removed from the app list 2016-01-21 16:24:02 -05:00
Cameron Gutman 2ec7d1d2f1 Use the main queue for the managed object context as a hack to prevent merge conflicts 2016-01-21 16:17:05 -05:00
Cameron Gutman ad1b9059ee Ensure the host matches for app entries 2016-01-21 15:01:15 -05:00
Cameron Gutman 4439c7cce6 Fix initial app list loading after pairing 2016-01-21 14:14:29 -05:00
Cameron Gutman b8a4093c75 Preserve app art when updating the app list 2016-01-21 14:03:07 -05:00
Cameron Gutman ec3748b26e Implement hash and isEqual for TemporaryApp and TemporaryHost 2016-01-21 13:34:34 -05:00
Cameron Gutman 1c1f22e047 Update the database on each server info poll 2016-01-21 12:09:49 -05:00
Cameron Gutman 9aa9b0fc5a Copy the pair state into the temporary host 2016-01-21 11:42:19 -05:00
Cameron Gutman f8e2ca8500 We can't hold onto managed objects in our temporary objects because they can disappear from under us, so do the lookup when we propagate values 2016-01-21 11:29:17 -05:00
Cameron Gutman 3076e29335 Basic database access is working 2015-12-02 00:25:38 -08:00
Cameron Gutman fe616d86c5 Reduce usage of DataManager 2015-12-01 20:56:08 -08:00
Cameron Gutman bfa4856bbe Move non-CoreData backed fields out of CoreData objects 2015-12-01 20:36:08 -08:00
Cameron Gutman 5ea92a7b48 Stop using CoreData-backed objects in any non-DataManager code 2015-12-01 20:31:24 -08:00
Diego Waxemberg 4ba71db7b2 bumped build version 2015-11-14 13:07:31 -08:00
Diego Waxemberg c1e6aeb43c Merge branch 'master' of github.com:moonlight-stream/moonlight-ios 2015-11-14 12:33:44 -08:00
Diego Waxemberg e3ffb66ef0 bumping version code 2015-11-14 12:33:41 -08:00
Cameron Gutman 5daa82b0ba Show the hosts in alphabetical order rather than in an undefined order 2015-11-13 19:55:18 -08:00
Cameron Gutman a8bcc6afba Split the work into 2 jobs per computer to retrieve assets faster 2015-11-13 19:52:35 -08:00
Cameron Gutman e6aa16ad01 Revert "Fix concurrent modification crash in app asset caching code"
This reverts commit ad0705d126.
2015-11-13 19:07:15 -08:00
Cameron Gutman ad0705d126 Fix concurrent modification crash in app asset caching code 2015-11-13 18:30:12 -08:00
Cameron Gutman 2f31f93bf8 Add support for GFE 2.8 2015-11-11 18:24:36 -08:00
Cameron Gutman ea3be613c5 Try 5 times to get the app list before failing 2015-11-11 18:14:59 -08:00
Cameron Gutman b495246f34 Only mark a PC offline if server info failed after 3 straight tries 2015-11-11 18:14:04 -08:00
Cameron Gutman 814470f4bf Set the default bitrate based on the selected resolution and FPS combo 2015-11-11 18:11:33 -08:00
Cameron Gutman 82293a08b6 Increase the request timeout to 7 seconds to give a bit more time for slow app lists to come in 2015-11-11 17:53:13 -08:00
Cameron Gutman 20d66336a7 Cache decoded UIImage objects for the scroll view to fix janky scrolling 2015-11-10 21:23:00 -08:00
Cameron Gutman c8441ec9fa Rasterize the cell layer to increase scrolling performance 2015-11-10 20:48:39 -08:00
Cameron Gutman f72135c227 Mark collection cells opaque for better performance 2015-11-10 20:47:09 -08:00
Michelle Bergeron 100e6e4ce8 Remove info about the beta 2015-11-09 21:53:58 -08:00
Michelle Bergeron ec8be20d62 Download link 2015-11-09 21:53:24 -08:00
Diego Waxemberg 55d48101ef Bumped build version
This is the first attempt at submission to the App Store
2015-11-01 00:12:39 -07:00
Cameron Gutman c36f4c5f84 Remove abort calls 2015-10-31 23:45:37 -07:00
Diego Waxemberg 06289ac4d2 No longer use IDFA, now generate UUID and persist in Core Data. Bumped version to 1.0.0 2015-10-31 23:04:32 -07:00
Cameron Gutman e6a2d5d425 Update project file after Xcode 7.1 2015-10-31 19:00:44 -07:00
Cameron Gutman ea8db9c4ea Update common with new bitrate logic 2015-10-31 19:00:05 -07:00
Cameron Gutman 3773e841d1 Update common to support GFE 2.7 2015-10-25 13:28:24 -07:00
Diego Waxemberg ad1ddd04f3 no longer allow multiple apps to be selected at the same time 2015-10-18 16:52:14 -07:00
Diego Waxemberg 67a8b232b9 added pull arrow to side of screen 2015-10-17 16:16:13 -07:00
Diego Waxemberg 5293c07fc0 fixed issue where we wouldn't terminate the stream on failure 2015-10-17 03:46:09 -07:00
Diego Waxemberg e3a42db007 On-screen controls revamp
- controls are bigger
- dead zone around buttons to avoid accidental mouse clicks and movement
- finished implementing control setup for AutoGCGamepad
- swiping to close stream requires swipe to 1/4 the screen width
- no longer send mouse movement if delta is too small
2015-10-17 03:02:55 -07:00
Diego Waxemberg 3b1462af22 removed support for native resolution stream 2015-10-16 23:38:14 -07:00
Cameron Gutman 74a0c5b1cd Merge branch 'master' of github.com:moonlight-stream/moonlight-ios 2015-09-30 20:44:26 -07:00
Cameron Gutman 05a5e4fa64 Fix a crash when apps are removed from GFE 2015-09-30 20:44:14 -07:00
Cameron Gutman b6445295a7 Use a temporary app object and database lock to avoid saving while the database is not in a consistent state 2015-09-30 20:34:52 -07:00
Diego Waxemberg 391605ef36 ignored the new xcsmblueprint files 2015-09-30 19:18:21 -07:00
Diego Waxemberg 695499dea9 host and app buttons now have feedback when tapped 2015-09-30 19:13:37 -07:00
Cameron Gutman 9ba7f35444 Require full-screen since the streaming game will be the primary interaction for the device 2015-09-30 18:00:52 -07:00
Cameron Gutman 37dc00735b Enabling bit code is causing linker errors when deploying to real devices for some reason. Turning it back off. 2015-09-29 00:06:27 -07:00
Cameron Gutman 920cbadc91 Add a workaround/hack to fix duplicating machines 2015-09-29 00:04:40 -07:00
Cameron Gutman bc53b4dc32 First stab at fixing on screen controls on various devices 2015-09-28 23:20:27 -07:00
Cameron Gutman 6015c38d17 Only display the native resolution dialog if the user has just chosen native resolution. Make the dialog a little nicer too. 2015-09-28 22:50:54 -07:00
Cameron Gutman 4ea8694a7d Fix status bar hiding while streaming on iPad 2015-09-28 21:49:46 -07:00
Cameron Gutman 00f01196fb Update placeholder app images to be the correct aspect ratio and invert the colors to better show against the new background 2015-09-28 21:40:10 -07:00
Diego Waxemberg 543639c93e Merge branch 'master' of github.com:moonlight-stream/moonlight-ios 2015-09-28 21:19:18 -07:00
Diego Waxemberg 1da877fed7 Changed theme to light/dark gray and updated launch screen. Also added a border and drop shadow to app icons 2015-09-28 21:18:35 -07:00
Cameron Gutman 396b5fc156 Update common 2015-09-28 19:22:28 -07:00
Cameron Gutman 297dfd4159 Update project file
- Change organization name
- Enable bit code
- Update Xcode compatibility version
2015-09-28 19:09:49 -07:00
Cameron Gutman 23e9d48b0d Update common-c and use the new LiInitializeXXX() functions 2015-08-16 12:27:21 -07:00
Cameron Gutman 9ec650a84f Zero audio renderer callbacks before populating 2015-08-12 01:18:22 -07:00
Cameron Gutman 90bdbc4ae0 Update common library 2015-08-12 01:11:38 -07:00
Cameron Gutman dd5f06723b Update common library to get assertions and a GFE 2.5.11 fix 2015-07-20 17:37:13 -07:00
Michelle Bergeron 55b2871b83 Update about beta program 2015-07-16 16:14:28 -07:00
90 changed files with 1507 additions and 506 deletions
+1
View File
@@ -1,2 +1,3 @@
**/xcuserdata/ **/xcuserdata/
**/Moonlight.xcscmblueprint
Build Build
+42 -27
View File
@@ -1,36 +1,51 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="8121.17" systemVersion="15A204h" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES"> <document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="8191" systemVersion="15A279b" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES">
<dependencies> <dependencies>
<deployment identifier="iOS"/> <deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="8101.14"/> <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="8154"/>
</dependencies> </dependencies>
<objects> <objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/> <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/> <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" id="iN0-l3-epB"> <navigationController id="vV9-32-ww6">
<rect key="frame" x="0.0" y="0.0" width="480" height="480"/> <simulatedOrientationMetrics key="simulatedOrientationMetrics" orientation="landscapeRight"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <navigationBar key="navigationBar" contentMode="scaleToFill" id="y2P-aE-Haa">
<subviews> <rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text=" Copyright (c) 2015 Moonlight Stream. All rights reserved." textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="9" translatesAutoresizingMaskIntoConstraints="NO" id="8ie-xW-0ye"> <autoresizingMask key="autoresizingMask"/>
<rect key="frame" x="18" y="439.5" width="444" height="20.5"/> <animations/>
<animations/> <color key="barTintColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/> </navigationBar>
<color key="textColor" red="0.72941176470000002" green="0.69411764710000001" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <viewControllers>
<nil key="highlightedColor"/> <viewController id="l6V-ue-5Om">
<variation key="widthClass=compact"> <layoutGuides>
<fontDescription key="fontDescription" type="system" pointSize="11"/> <viewControllerLayoutGuide type="top" id="rEZ-vx-akr"/>
</variation> <viewControllerLayoutGuide type="bottom" id="gBD-cy-cth"/>
</label> </layoutGuides>
</subviews> <view key="view" contentMode="scaleToFill" id="6lF-fc-kpK">
<animations/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/> <animations/>
<constraints> <color key="backgroundColor" white="0.33333333333333331" alpha="1" colorSpace="calibratedWhite"/>
<constraint firstAttribute="bottom" secondItem="8ie-xW-0ye" secondAttribute="bottom" constant="20" id="Gnz-uJ-CDw"/> </view>
<constraint firstAttribute="centerX" secondItem="8ie-xW-0ye" secondAttribute="centerX" id="ZEH-qu-HZ9"/> <navigationItem key="navigationItem" id="rW0-BH-s7l">
</constraints> <barButtonItem key="leftBarButtonItem" enabled="NO" id="d9R-Zo-Uml">
<nil key="simulatedStatusBarMetrics"/> <button key="customView" opaque="NO" contentMode="center" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" id="9JC-B5-BAE">
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/> <rect key="frame" x="-23" y="-15" width="46" height="30"/>
<point key="canvasLocation" x="404" y="445"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
</view> <animations/>
<fontDescription key="fontDescription" type="system" pointSize="16"/>
<state key="normal" title="Moonlight" image="Logo">
<color key="titleColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
<color key="titleShadowColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
</state>
<variation key="widthClass=regular" preservesSuperviewLayoutMargins="NO"/>
</button>
</barButtonItem>
</navigationItem>
</viewController>
</viewControllers>
</navigationController>
</objects> </objects>
<resources>
<image name="Logo" width="32" height="32"/>
</resources>
</document> </document>
+13
View File
@@ -0,0 +1,13 @@
//
// AppCollectionView.h
// Moonlight
//
// Created by Diego Waxemberg on 9/30/15.
// Copyright © 2015 Moonlight Stream. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface AppCollectionView : UICollectionView
@end
+28
View File
@@ -0,0 +1,28 @@
//
// AppCollectionView.m
// Moonlight
//
// Created by Diego Waxemberg on 9/30/15.
// Copyright © 2015 Moonlight Stream. All rights reserved.
//
#import "AppCollectionView.h"
@implementation AppCollectionView
- (BOOL)touchesShouldCancelInContentView:(UIView *)view {
if ([view isKindOfClass:[UIButton class]]) {
return YES;
}
return [super touchesShouldCancelInContentView:view];
}
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
// Drawing code
}
*/
@end
+10 -30
View File
@@ -83,10 +83,7 @@ static NSOperationQueue* mainQueue;
NSManagedObjectContext *managedObjectContext = self.managedObjectContext; NSManagedObjectContext *managedObjectContext = self.managedObjectContext;
if (managedObjectContext != nil) { if (managedObjectContext != nil) {
if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error]) { if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error]) {
// Replace this implementation with code to handle the error appropriately. Log(LOG_E, @"Critical database error: %@, %@", error, [error userInfo]);
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
Log(LOG_E, @"Unresolved error %@, %@", error, [error userInfo]);
abort();
} }
} }
} }
@@ -136,32 +133,15 @@ static NSOperationQueue* mainQueue;
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption, [NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil]; [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error]) { if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error]) {
/* // Log the error
Replace this implementation with code to handle the error appropriately. Log(LOG_E, @"Critical database error: %@, %@", error, [error userInfo]);
abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. // Drop the database
[[NSFileManager defaultManager] removeItemAtURL:storeURL error:nil];
Typical reasons for an error here include:
* The persistent store is not accessible; // Try again
* The schema for the persistent store is incompatible with current managed object model. return [self persistentStoreCoordinator];
Check the error message to determine what the actual problem was. }
If the persistent store is not accessible, there is typically something wrong with the file path. Often, a file URL is pointing into the application's resources directory instead of a writeable directory.
If you encounter schema incompatibility errors during development, you can reduce their frequency by:
* Simply deleting the existing store:
[[NSFileManager defaultManager] removeItemAtURL:storeURL error:nil]
* Performing automatic lightweight migration by passing the following dictionary as the options parameter:
@{NSMigratePersistentStoresAutomaticallyOption:@YES, NSInferMappingModelAutomaticallyOption:@YES}
Lightweight migration will only work for a limited set of schema changes; consult "Core Data Model Versioning and Data Migration Programming Guide" for details.
*/
Log(LOG_E, @"Unresolved error %@, %@", error, [error userInfo]);
abort();
}
return _persistentStoreCoordinator; return _persistentStoreCoordinator;
} }
+13
View File
@@ -0,0 +1,13 @@
//
// ComputerScrollView.h
// Moonlight
//
// Created by Diego Waxemberg on 9/30/15.
// Copyright © 2015 Moonlight Stream. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface ComputerScrollView : UIScrollView
@end
+28
View File
@@ -0,0 +1,28 @@
//
// ComputerScrollView.m
// Moonlight
//
// Created by Diego Waxemberg on 9/30/15.
// Copyright © 2015 Moonlight Stream. All rights reserved.
//
#import "ComputerScrollView.h"
@implementation ComputerScrollView
- (BOOL)touchesShouldCancelInContentView:(UIView *)view {
if ([view isKindOfClass:[UIButton class]]) {
return YES;
}
return [super touchesShouldCancelInContentView:view];
}
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
// Drawing code
}
*/
@end
-1
View File
@@ -11,7 +11,6 @@
@interface CryptoManager : NSObject @interface CryptoManager : NSObject
+ (void) generateKeyPairUsingSSl; + (void) generateKeyPairUsingSSl;
+ (NSString*) getUniqueID;
+ (NSData*) readCertFromFile; + (NSData*) readCertFromFile;
+ (NSData*) readKeyFromFile; + (NSData*) readKeyFromFile;
+ (NSData*) readP12FromFile; + (NSData*) readP12FromFile;
-15
View File
@@ -8,7 +8,6 @@
#import "CryptoManager.h" #import "CryptoManager.h"
#import "mkcert.h" #import "mkcert.h"
#import <AdSupport/ASIdentifierManager.h>
#include <openssl/aes.h> #include <openssl/aes.h>
#include <openssl/sha.h> #include <openssl/sha.h>
@@ -229,18 +228,4 @@ static NSData* p12 = nil;
}); });
} }
+ (NSString*) getUniqueID {
// generate a UUID
NSUUID* uuid = [ASIdentifierManager sharedManager].advertisingIdentifier;
NSString* idString = [NSString stringWithString:[uuid UUIDString]];
// we need a 16byte hex-string so we take the last 17 characters
// and remove the '-' to get a 16 character string
NSMutableString* uniqueId = [NSMutableString stringWithString:[idString substringFromIndex:19]];
[uniqueId deleteCharactersInRange:NSMakeRange(4, 1)];
//Log(LOG_D, @"Unique ID: %@", uniqueId);
return [NSString stringWithString:uniqueId];
}
@end @end
+15
View File
@@ -0,0 +1,15 @@
//
// IdManager.h
// Moonlight
//
// Created by Diego Waxemberg on 10/31/15.
// Copyright © 2015 Moonlight Stream. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface IdManager : NSObject
+ (NSString*) getUniqueId;
@end
+32
View File
@@ -0,0 +1,32 @@
//
// IdManager.m
// Moonlight
//
// Created by Diego Waxemberg on 10/31/15.
// Copyright © 2015 Moonlight Stream. All rights reserved.
//
#import "IdManager.h"
#import "DataManager.h"
@implementation IdManager
+ (NSString*) getUniqueId {
DataManager* dataMan = [[DataManager alloc] init];
NSString* uniqueId = [dataMan getUniqueId];
if (uniqueId == nil) {
uniqueId = [IdManager generateUniqueId];
[dataMan updateUniqueId:uniqueId];
Log(LOG_I, @"No UUID found. Generated new UUID: %@", uniqueId);
}
return uniqueId;
}
+ (NSString*) generateUniqueId {
UInt64 uuidLong = ((UInt64) arc4random() << 32) | arc4random();
return [NSString stringWithFormat:@"%016llx", uuidLong];
}
@end
-2
View File
@@ -85,7 +85,6 @@ int mkcert(X509 **x509p, EVP_PKEY **pkeyp, int bits, int serial, int years) {
if (*pkeyp == NULL) { if (*pkeyp == NULL) {
if ((pk=EVP_PKEY_new()) == NULL) { if ((pk=EVP_PKEY_new()) == NULL) {
abort();
return(0); return(0);
} }
} else { } else {
@@ -102,7 +101,6 @@ int mkcert(X509 **x509p, EVP_PKEY **pkeyp, int bits, int serial, int years) {
rsa = RSA_generate_key(bits, RSA_F4, NULL, NULL); rsa = RSA_generate_key(bits, RSA_F4, NULL, NULL);
if (!EVP_PKEY_assign_RSA(pk, rsa)) { if (!EVP_PKEY_assign_RSA(pk, rsa)) {
abort();
goto err; goto err;
} }
-8
View File
@@ -11,16 +11,8 @@
@class Host; @class Host;
NS_ASSUME_NONNULL_BEGIN
@interface App : NSManagedObject @interface App : NSManagedObject
@property BOOL isRunning;
- (NSComparisonResult)compareName:(App *)other;
@end @end
NS_ASSUME_NONNULL_END
#import "App+CoreDataProperties.h" #import "App+CoreDataProperties.h"
-7
View File
@@ -7,14 +7,7 @@
// //
#import "App.h" #import "App.h"
#import "Host.h"
@implementation App @implementation App
@synthesize isRunning;
- (NSComparisonResult)compareName:(App *)other {
return [self.name caseInsensitiveCompare:other.name];
}
@end @end
+15 -12
View File
@@ -7,22 +7,25 @@
// //
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
#import "Settings.h"
#import "AppDelegate.h" #import "AppDelegate.h"
#import "Host.h" #import "TemporaryHost.h"
#import "App.h" #import "TemporaryApp.h"
#import "TemporarySettings.h"
@interface DataManager : NSObject @interface DataManager : NSObject
@property (strong, nonatomic) AppDelegate* appDelegate;
- (void) saveSettingsWithBitrate:(NSInteger)bitrate framerate:(NSInteger)framerate height:(NSInteger)height width:(NSInteger)width onscreenControls:(NSInteger)onscreenControls; - (void) saveSettingsWithBitrate:(NSInteger)bitrate framerate:(NSInteger)framerate height:(NSInteger)height width:(NSInteger)width onscreenControls:(NSInteger)onscreenControls;
- (Settings*) retrieveSettings;
- (NSArray*) retrieveHosts; - (NSArray*) getHosts;
- (void) saveData; - (void) updateHost:(TemporaryHost*)host;
- (Host*) createHost; - (void) updateAppsForExistingHost:(TemporaryHost *)host;
- (void) removeHost:(Host*)host; - (void) updateIconForExistingApp:(TemporaryApp*)app;
- (App*) createApp; - (void) removeHost:(TemporaryHost*)host;
- (void) removeApp:(App*)app; - (void) removeApp:(TemporaryApp*)app;
- (TemporarySettings*) getSettings;
- (void) updateUniqueId:(NSString*)uniqueId;
- (NSString*) getUniqueId;
@end @end
+169 -36
View File
@@ -7,37 +7,131 @@
// //
#import "DataManager.h" #import "DataManager.h"
#import "TemporaryApp.h"
#import "TemporarySettings.h"
#import "Settings.h"
@implementation DataManager @implementation DataManager {
NSManagedObjectContext *_managedObjectContext;
AppDelegate *_appDelegate;
}
- (id) init { - (id) init {
self = [super init]; self = [super init];
self.appDelegate = [[UIApplication sharedApplication] delegate];
_appDelegate = [[UIApplication sharedApplication] delegate];
_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[_managedObjectContext setPersistentStoreCoordinator:_appDelegate.persistentStoreCoordinator];
return self; return self;
} }
- (void) saveSettingsWithBitrate:(NSInteger)bitrate framerate:(NSInteger)framerate height:(NSInteger)height width:(NSInteger)width onscreenControls:(NSInteger)onscreenControls { - (void) updateUniqueId:(NSString*)uniqueId {
Settings* settingsToSave = [self retrieveSettings]; [_managedObjectContext performBlockAndWait:^{
settingsToSave.framerate = [NSNumber numberWithInteger:framerate]; [self retrieveSettings].uniqueId = uniqueId;
// Bitrate is persisted in kbps [self saveData];
settingsToSave.bitrate = [NSNumber numberWithInteger:bitrate]; }];
settingsToSave.height = [NSNumber numberWithInteger:height]; }
settingsToSave.width = [NSNumber numberWithInteger:width];
settingsToSave.onscreenControls = [NSNumber numberWithInteger:onscreenControls];
NSError* error; - (NSString*) getUniqueId {
if (![[self.appDelegate managedObjectContext] save:&error]) { __block NSString *uid;
Log(LOG_E, @"Unable to save settings to database: %@", error);
} [_managedObjectContext performBlockAndWait:^{
[self.appDelegate saveContext]; uid = [self retrieveSettings].uniqueId;
}];
return uid;
}
- (void) saveSettingsWithBitrate:(NSInteger)bitrate framerate:(NSInteger)framerate height:(NSInteger)height width:(NSInteger)width onscreenControls:(NSInteger)onscreenControls {
[_managedObjectContext performBlockAndWait:^{
Settings* settingsToSave = [self retrieveSettings];
settingsToSave.framerate = [NSNumber numberWithInteger:framerate];
// Bitrate is persisted in kbps
settingsToSave.bitrate = [NSNumber numberWithInteger:bitrate];
settingsToSave.height = [NSNumber numberWithInteger:height];
settingsToSave.width = [NSNumber numberWithInteger:width];
settingsToSave.onscreenControls = [NSNumber numberWithInteger:onscreenControls];
[self saveData];
}];
}
- (void) updateHost:(TemporaryHost *)host {
[_managedObjectContext performBlockAndWait:^{
// Add a new persistent managed object if one doesn't exist
Host* parent = [self getHostForTemporaryHost:host];
if (parent == nil) {
NSEntityDescription* entity = [NSEntityDescription entityForName:@"Host" inManagedObjectContext:_managedObjectContext];
parent = [[Host alloc] initWithEntity:entity insertIntoManagedObjectContext:_managedObjectContext];
}
// Push changes from the temp host to the persistent one
[host propagateChangesToParent:parent];
[self saveData];
}];
}
- (void) updateAppsForExistingHost:(TemporaryHost *)host {
[_managedObjectContext performBlockAndWait:^{
Host* parent = [self getHostForTemporaryHost:host];
if (parent == nil) {
// The host must exist to be updated
return;
}
NSMutableSet *applist = [[NSMutableSet alloc] init];
for (TemporaryApp* app in host.appList) {
// Add a new persistent managed object if one doesn't exist
App* parentApp = [self getAppForTemporaryApp:app];
if (parentApp == nil) {
NSEntityDescription* entity = [NSEntityDescription entityForName:@"App" inManagedObjectContext:_managedObjectContext];
parentApp = [[App alloc] initWithEntity:entity insertIntoManagedObjectContext:_managedObjectContext];
}
[app propagateChangesToParent:parentApp withHost:parent];
[applist addObject:parentApp];
}
parent.appList = applist;
[self saveData];
}];
}
- (void) updateIconForExistingApp:(TemporaryApp*)app {
[_managedObjectContext performBlockAndWait:^{
App* parentApp = [self getAppForTemporaryApp:app];
if (parentApp == nil) {
// The app must exist to be updated
return;
}
parentApp.image = app.image;
[self saveData];
}];
}
- (TemporarySettings*) getSettings {
__block TemporarySettings *tempSettings;
[_managedObjectContext performBlockAndWait:^{
tempSettings = [[TemporarySettings alloc] initFromSettings:[self retrieveSettings]];
}];
return tempSettings;
} }
- (Settings*) retrieveSettings { - (Settings*) retrieveSettings {
NSArray* fetchedRecords = [self fetchRecords:@"Settings"]; NSArray* fetchedRecords = [self fetchRecords:@"Settings"];
if (fetchedRecords.count == 0) { if (fetchedRecords.count == 0) {
// create a new settings object with the default values // create a new settings object with the default values
NSEntityDescription* entity = [NSEntityDescription entityForName:@"Settings" inManagedObjectContext:[self.appDelegate managedObjectContext]]; NSEntityDescription* entity = [NSEntityDescription entityForName:@"Settings" inManagedObjectContext:_managedObjectContext];
Settings* settings = [[Settings alloc] initWithEntity:entity insertIntoManagedObjectContext:[self.appDelegate managedObjectContext]]; Settings* settings = [[Settings alloc] initWithEntity:entity insertIntoManagedObjectContext:_managedObjectContext];
return settings; return settings;
} else { } else {
@@ -46,49 +140,88 @@
} }
} }
- (Host*) createHost { - (void) removeApp:(TemporaryApp*)app {
NSEntityDescription* entity = [NSEntityDescription entityForName:@"Host" inManagedObjectContext:[self.appDelegate managedObjectContext]]; [_managedObjectContext performBlockAndWait:^{
return [[Host alloc] initWithEntity:entity insertIntoManagedObjectContext:[self.appDelegate managedObjectContext]]; App* managedApp = [self getAppForTemporaryApp:app];
if (managedApp != nil) {
[_managedObjectContext deleteObject:managedApp];
[self saveData];
}
}];
} }
- (void) removeHost:(Host*)host { - (void) removeHost:(TemporaryHost*)host {
[[self.appDelegate managedObjectContext] deleteObject:host]; [_managedObjectContext performBlockAndWait:^{
[self saveData]; Host* managedHost = [self getHostForTemporaryHost:host];
if (managedHost != nil) {
[_managedObjectContext deleteObject:managedHost];
[self saveData];
}
}];
} }
- (void) saveData { - (void) saveData {
NSError* error; NSError* error;
if (![[self.appDelegate managedObjectContext] save:&error]) { if (![_managedObjectContext save:&error]) {
Log(LOG_E, @"Unable to save hosts to database: %@", error); Log(LOG_E, @"Unable to save hosts to database: %@", error);
} }
[self.appDelegate saveContext];
[_appDelegate saveContext];
} }
- (NSArray*) retrieveHosts { - (NSArray*) getHosts {
return [self fetchRecords:@"Host"]; __block NSMutableArray *tempHosts = [[NSMutableArray alloc] init];
[_managedObjectContext performBlockAndWait:^{
NSArray *hosts = [self fetchRecords:@"Host"];
for (Host* host in hosts) {
[tempHosts addObject:[[TemporaryHost alloc] initFromHost:host]];
}
}];
return tempHosts;
} }
- (App*) createApp { // Only call from within performBlockAndWait!!!
NSEntityDescription* entity = [NSEntityDescription entityForName:@"App" inManagedObjectContext:[self.appDelegate managedObjectContext]]; - (Host*) getHostForTemporaryHost:(TemporaryHost*)tempHost {
return [[App alloc] initWithEntity:entity insertIntoManagedObjectContext:[self.appDelegate managedObjectContext]]; NSArray *hosts = [self fetchRecords:@"Host"];
for (Host* host in hosts) {
if ([tempHost.uuid isEqualToString:host.uuid]) {
return host;
}
}
return nil;
} }
- (void) removeApp:(App*)app { // Only call from within performBlockAndWait!!!
[[self.appDelegate managedObjectContext] deleteObject:app]; - (App*) getAppForTemporaryApp:(TemporaryApp*)tempApp {
NSArray *apps = [self fetchRecords:@"App"];
for (App* app in apps) {
if ([app.id isEqualToString:tempApp.id] &&
[app.host.uuid isEqualToString:app.host.uuid]) {
return app;
}
}
return nil;
} }
- (NSArray*) fetchRecords:(NSString*)entityName { - (NSArray*) fetchRecords:(NSString*)entityName {
NSArray* fetchedRecords;
NSFetchRequest* fetchRequest = [[NSFetchRequest alloc] init]; NSFetchRequest* fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription* entity = [NSEntityDescription entityForName:entityName inManagedObjectContext:[self.appDelegate managedObjectContext]]; NSEntityDescription* entity = [NSEntityDescription entityForName:entityName inManagedObjectContext:_managedObjectContext];
[fetchRequest setEntity:entity]; [fetchRequest setEntity:entity];
[fetchRequest setAffectedStores:[NSArray arrayWithObjects:[[self.appDelegate persistentStoreCoordinator] persistentStoreForURL:[self.appDelegate getStoreURL]], nil]];
NSError* error; NSError* error;
NSArray* fetchedRecords = [[self.appDelegate managedObjectContext] executeFetchRequest:fetchRequest error:&error]; fetchedRecords = [_managedObjectContext executeFetchRequest:fetchRequest error:&error];
//TODO: handle errors //TODO: handle errors
return fetchedRecords; return fetchedRecords;
} }
@end @end
-8
View File
@@ -10,16 +10,8 @@
#import <CoreData/CoreData.h> #import <CoreData/CoreData.h>
#import "Utils.h" #import "Utils.h"
NS_ASSUME_NONNULL_BEGIN
@interface Host : NSManagedObject @interface Host : NSManagedObject
@property (nonatomic) BOOL online;
@property (nonatomic) PairState pairState;
@property (nonatomic) NSString * activeAddress;
@end @end
NS_ASSUME_NONNULL_END
#import "Host+CoreDataProperties.h" #import "Host+CoreDataProperties.h"
-4
View File
@@ -10,8 +10,4 @@
@implementation Host @implementation Host
@dynamic pairState;
@synthesize online;
@synthesize activeAddress;
@end @end
+1
View File
@@ -17,5 +17,6 @@
@property (nonatomic, retain) NSNumber * height; @property (nonatomic, retain) NSNumber * height;
@property (nonatomic, retain) NSNumber * width; @property (nonatomic, retain) NSNumber * width;
@property (nonatomic, retain) NSNumber * onscreenControls; @property (nonatomic, retain) NSNumber * onscreenControls;
@property (nonatomic, retain) NSString * uniqueId;
@end @end
+1
View File
@@ -16,5 +16,6 @@
@dynamic height; @dynamic height;
@dynamic width; @dynamic width;
@dynamic onscreenControls; @dynamic onscreenControls;
@dynamic uniqueId;
@end @end
+31
View File
@@ -0,0 +1,31 @@
//
// TemporaryApp.h
// Moonlight
//
// Created by Cameron Gutman on 9/30/15.
// Copyright © 2015 Moonlight Stream. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "TemporaryHost.h"
#import "App.h"
@interface TemporaryApp : NSObject
@property (nullable, nonatomic, retain) NSString *id;
@property (nullable, nonatomic, retain) NSData *image;
@property (nullable, nonatomic, retain) NSString *name;
@property (nonatomic) BOOL isRunning;
@property (nullable, nonatomic, retain) TemporaryHost *host;
NS_ASSUME_NONNULL_BEGIN
- (id) initFromApp:(App*)app withTempHost:(TemporaryHost*)tempHost;
- (NSComparisonResult)compareName:(TemporaryApp *)other;
- (void) propagateChangesToParent:(App*)parent withHost:(Host*)host;
NS_ASSUME_NONNULL_END
@end
+52
View File
@@ -0,0 +1,52 @@
//
// TemporaryApp.m
// Moonlight
//
// Created by Cameron Gutman on 9/30/15.
// Copyright © 2015 Moonlight Stream. All rights reserved.
//
#import "TemporaryApp.h"
#import "App.h"
@implementation TemporaryApp
- (id) initFromApp:(App*)app withTempHost:(TemporaryHost*)tempHost {
self = [self init];
self.id = app.id;
self.image = app.image;
self.name = app.name;
self.host = tempHost;
return self;
}
- (void) propagateChangesToParent:(App*)parent withHost:(Host*)host {
parent.id = self.id;
parent.name = self.name;
parent.host = host;
}
- (NSComparisonResult)compareName:(TemporaryApp *)other {
return [self.name caseInsensitiveCompare:other.name];
}
- (NSUInteger)hash {
return [self.host.uuid hash] * 31 + [self.id intValue];
}
- (BOOL)isEqual:(id)object {
if (self == object) {
return YES;
}
if (![object isKindOfClass:[App class]]) {
return NO;
}
return [self.host.uuid isEqualToString:((App*)object).host.uuid] &&
[self.id isEqualToString:((App*)object).id];
}
@end
+37
View File
@@ -0,0 +1,37 @@
//
// TemporaryHost.h
// Moonlight
//
// Created by Cameron Gutman on 12/1/15.
// Copyright © 2015 Moonlight Stream. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "Utils.h"
#import "Host.h"
@interface TemporaryHost : NSObject
@property (nonatomic) BOOL online;
@property (nonatomic) PairState pairState;
@property (nonatomic, nullable) NSString * activeAddress;
NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, retain) NSString *address;
@property (nonatomic, retain) NSString *externalAddress;
@property (nonatomic, retain) NSString *localAddress;
@property (nonatomic, retain) NSString *mac;
@property (nonatomic, retain) NSString *name;
@property (nonatomic, retain) NSString *uuid;
@property (nonatomic, retain) NSMutableSet *appList;
- (id) initFromHost:(Host*)host;
- (NSComparisonResult)compareName:(TemporaryHost *)other;
- (void) propagateChangesToParent:(Host*)host;
NS_ASSUME_NONNULL_END
@end
+77
View File
@@ -0,0 +1,77 @@
//
// TemporaryHost.m
// Moonlight
//
// Created by Cameron Gutman on 12/1/15.
// Copyright © 2015 Moonlight Stream. All rights reserved.
//
#import "DataManager.h"
#import "TemporaryHost.h"
#import "Host.h"
#import "TemporaryApp.h"
#import "App.h"
@implementation TemporaryHost
- (id) init {
self = [super init];
self.appList = [[NSMutableSet alloc] init];
return self;
}
- (id) initFromHost:(Host*)host {
self = [self init];
self.address = host.address;
self.externalAddress = host.externalAddress;
self.localAddress = host.localAddress;
self.mac = host.mac;
self.name = host.name;
self.uuid = host.uuid;
self.pairState = [host.pairState intValue];
NSMutableSet *appList = [[NSMutableSet alloc] init];
for (App* app in host.appList) {
TemporaryApp *tempApp = [[TemporaryApp alloc] initFromApp:app withTempHost:self];
[appList addObject:tempApp];
}
self.appList = appList;
return self;
}
- (void) propagateChangesToParent:(Host*)parentHost {
parentHost.address = self.address;
parentHost.externalAddress = self.externalAddress;
parentHost.localAddress = self.localAddress;
parentHost.mac = self.mac;
parentHost.name = self.name;
parentHost.uuid = self.uuid;
parentHost.pairState = [NSNumber numberWithInt:self.pairState];
}
- (NSComparisonResult)compareName:(TemporaryHost *)other {
return [self.name caseInsensitiveCompare:other.name];
}
- (NSUInteger)hash {
return [self.uuid hash];
}
- (BOOL)isEqual:(id)object {
if (self == object) {
return YES;
}
if (![object isKindOfClass:[Host class]]) {
return NO;
}
return [self.uuid isEqualToString:((Host*)object).uuid];
}
@end
+25
View File
@@ -0,0 +1,25 @@
//
// TemporarySettings.h
// Moonlight
//
// Created by Cameron Gutman on 12/1/15.
// Copyright © 2015 Moonlight Stream. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "Settings.h"
@interface TemporarySettings : NSObject
@property (nonatomic, retain) Settings * parent;
@property (nonatomic, retain) NSNumber * bitrate;
@property (nonatomic, retain) NSNumber * framerate;
@property (nonatomic, retain) NSNumber * height;
@property (nonatomic, retain) NSNumber * width;
@property (nonatomic, retain) NSNumber * onscreenControls;
@property (nonatomic, retain) NSString * uniqueId;
- (id) initFromSettings:(Settings*)settings;
@end
+28
View File
@@ -0,0 +1,28 @@
//
// TemporarySettings.m
// Moonlight
//
// Created by Cameron Gutman on 12/1/15.
// Copyright © 2015 Moonlight Stream. All rights reserved.
//
#import "TemporarySettings.h"
@implementation TemporarySettings
- (id) initFromSettings:(Settings*)settings {
self = [self init];
self.parent = settings;
self.bitrate = settings.bitrate;
self.framerate = settings.framerate;
self.height = settings.height;
self.width = settings.width;
self.onscreenControls = settings.onscreenControls;
self.uniqueId = settings.uniqueId;
return self;
}
@end
+5 -6
View File
@@ -2,18 +2,17 @@
"images" : [ "images" : [
{ {
"idiom" : "universal", "idiom" : "universal",
"scale" : "1x", "filename" : "[email protected]",
"filename" : "[email protected]" "scale" : "1x"
}, },
{ {
"idiom" : "universal", "idiom" : "universal",
"scale" : "2x", "filename" : "[email protected]",
"filename" : "[email protected]" "scale" : "2x"
}, },
{ {
"idiom" : "universal", "idiom" : "universal",
"scale" : "3x", "scale" : "3x"
"filename" : "[email protected]"
} }
], ],
"info" : { "info" : {
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

+5 -6
View File
@@ -2,18 +2,17 @@
"images" : [ "images" : [
{ {
"idiom" : "universal", "idiom" : "universal",
"scale" : "1x", "filename" : "[email protected]",
"filename" : "[email protected]" "scale" : "1x"
}, },
{ {
"idiom" : "universal", "idiom" : "universal",
"scale" : "2x", "filename" : "[email protected]",
"filename" : "[email protected]" "scale" : "2x"
}, },
{ {
"idiom" : "universal", "idiom" : "universal",
"scale" : "3x", "scale" : "3x"
"filename" : "[email protected]"
} }
], ],
"info" : { "info" : {
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

+6
View File
@@ -0,0 +1,6 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}
@@ -2,18 +2,17 @@
"images" : [ "images" : [
{ {
"idiom" : "universal", "idiom" : "universal",
"scale" : "1x", "filename" : "[email protected]",
"filename" : "[email protected]" "scale" : "1x"
}, },
{ {
"idiom" : "universal", "idiom" : "universal",
"scale" : "2x", "filename" : "[email protected]",
"filename" : "[email protected]" "scale" : "2x"
}, },
{ {
"idiom" : "universal", "idiom" : "universal",
"scale" : "3x", "scale" : "3x"
"filename" : "[email protected]"
} }
], ],
"info" : { "info" : {
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

@@ -2,18 +2,17 @@
"images" : [ "images" : [
{ {
"idiom" : "universal", "idiom" : "universal",
"scale" : "1x", "filename" : "[email protected]",
"filename" : "[email protected]" "scale" : "1x"
}, },
{ {
"idiom" : "universal", "idiom" : "universal",
"scale" : "2x", "filename" : "[email protected]",
"filename" : "[email protected]" "scale" : "2x"
}, },
{ {
"idiom" : "universal", "idiom" : "universal",
"scale" : "3x", "scale" : "3x"
"filename" : "[email protected]"
} }
], ],
"info" : { "info" : {
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

@@ -0,0 +1,23 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "[email protected]",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "[email protected]",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "[email protected]",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 901 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 812 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

@@ -2,18 +2,17 @@
"images" : [ "images" : [
{ {
"idiom" : "universal", "idiom" : "universal",
"scale" : "1x", "filename" : "[email protected]",
"filename" : "[email protected]" "scale" : "1x"
}, },
{ {
"idiom" : "universal", "idiom" : "universal",
"scale" : "2x", "filename" : "[email protected]",
"filename" : "[email protected]" "scale" : "2x"
}, },
{ {
"idiom" : "universal", "idiom" : "universal",
"scale" : "3x", "scale" : "3x"
"filename" : "[email protected]"
} }
], ],
"info" : { "info" : {
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

+5 -6
View File
@@ -2,18 +2,17 @@
"images" : [ "images" : [
{ {
"idiom" : "universal", "idiom" : "universal",
"scale" : "1x", "filename" : "[email protected]",
"filename" : "[email protected]" "scale" : "1x"
}, },
{ {
"idiom" : "universal", "idiom" : "universal",
"scale" : "2x", "filename" : "[email protected]",
"filename" : "[email protected]" "scale" : "2x"
}, },
{ {
"idiom" : "universal", "idiom" : "universal",
"scale" : "3x", "scale" : "3x"
"filename" : "[email protected]"
} }
], ],
"info" : { "info" : {
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

+5 -6
View File
@@ -2,18 +2,17 @@
"images" : [ "images" : [
{ {
"idiom" : "universal", "idiom" : "universal",
"scale" : "1x", "filename" : "x[email protected]",
"filename" : "x[email protected]" "scale" : "1x"
}, },
{ {
"idiom" : "universal", "idiom" : "universal",
"scale" : "2x", "filename" : "x[email protected]",
"filename" : "x[email protected]" "scale" : "2x"
}, },
{ {
"idiom" : "universal", "idiom" : "universal",
"scale" : "3x", "scale" : "3x"
"filename" : "[email protected]"
} }
], ],
"info" : { "info" : {
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

+5 -6
View File
@@ -2,18 +2,17 @@
"images" : [ "images" : [
{ {
"idiom" : "universal", "idiom" : "universal",
"scale" : "1x", "filename" : "[email protected]",
"filename" : "[email protected]" "scale" : "1x"
}, },
{ {
"idiom" : "universal", "idiom" : "universal",
"scale" : "2x", "filename" : "[email protected]",
"filename" : "[email protected]" "scale" : "2x"
}, },
{ {
"idiom" : "universal", "idiom" : "universal",
"scale" : "3x", "scale" : "3x"
"filename" : "[email protected]"
} }
], ],
"info" : { "info" : {
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

+244 -61
View File
@@ -63,25 +63,30 @@
BOOL l3Set; BOOL l3Set;
BOOL r3Set; BOOL r3Set;
BOOL _iPad;
CGRect _controlArea;
UIView* _view; UIView* _view;
OnScreenControlsLevel _level; OnScreenControlsLevel _level;
ControllerSupport *_controllerSupport; ControllerSupport *_controllerSupport;
Controller *_controller; Controller *_controller;
id<EdgeDetectionDelegate> _edgeDelegate; id<EdgeDetectionDelegate> _edgeDelegate;
NSMutableArray* _deadTouches;
} }
static const float EDGE_WIDTH = .1; static const float EDGE_WIDTH = .05;
//static const float BUTTON_SIZE = 50; //static const float BUTTON_SIZE = 50;
static const float BUTTON_DIST = 20; static const float BUTTON_DIST = 20;
static float BUTTON_CENTER_X; static float BUTTON_CENTER_X;
static float BUTTON_CENTER_Y; static float BUTTON_CENTER_Y;
static const float D_PAD_DIST = 15; static const float D_PAD_DIST = 10;
static float D_PAD_CENTER_X; static float D_PAD_CENTER_X;
static float D_PAD_CENTER_Y; static float D_PAD_CENTER_Y;
static const float DEAD_ZONE_PADDING = 15;
static const double STICK_CLICK_RATE = 100; static const double STICK_CLICK_RATE = 100;
static const float STICK_DEAD_ZONE = .1; static const float STICK_DEAD_ZONE = .1;
static float STICK_INNER_SIZE; static float STICK_INNER_SIZE;
@@ -117,6 +122,21 @@ static float L3_Y;
_controller = [[Controller alloc] init]; _controller = [[Controller alloc] init];
_controller.playerIndex = 0; _controller.playerIndex = 0;
_edgeDelegate = swipeDelegate; _edgeDelegate = swipeDelegate;
_deadTouches = [[NSMutableArray alloc] init];
_iPad = (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad);
_controlArea = CGRectMake(0, 0, _view.frame.size.width, _view.frame.size.height);
if (_iPad)
{
// Cut down the control area on an iPad so the controls are more reachable
_controlArea.size.height = _view.frame.size.height / 2.0;
_controlArea.origin.y = _view.frame.size.height - _controlArea.size.height;
}
else
{
_controlArea.origin.x = _controlArea.size.width * EDGE_WIDTH;
_controlArea.size.width -= _controlArea.origin.x * 2;
}
_aButton = [CALayer layer]; _aButton = [CALayer layer];
_bButton = [CALayer layer]; _bButton = [CALayer layer];
@@ -139,7 +159,7 @@ static float L3_Y;
_leftStick = [CALayer layer]; _leftStick = [CALayer layer];
_rightStick = [CALayer layer]; _rightStick = [CALayer layer];
_edge = [CALayer layer]; _edge = [CALayer layer];
[self setupEdgeDetection]; [self setupEdgeDetection];
return self; return self;
@@ -167,10 +187,10 @@ static float L3_Y;
[self hideButtons]; [self hideButtons];
[self hideBumpers]; [self hideBumpers];
[self hideL3R3];
[self drawTriggers]; [self drawTriggers];
[self drawStartSelect]; [self drawStartSelect];
[self drawSticks]; [self drawSticks];
[self drawL3R3];
break; break;
case OnScreenControlsLevelAutoGCExtendedGamepad: case OnScreenControlsLevelAutoGCExtendedGamepad:
// GCExtendedGamepad is missing R3, L3, and select // GCExtendedGamepad is missing R3, L3, and select
@@ -219,8 +239,8 @@ static float L3_Y;
// Start with the default complex layout // Start with the default complex layout
[self setupComplexControls]; [self setupComplexControls];
START_Y = _view.frame.size.height * .9; START_Y = _controlArea.size.height * .9 + _controlArea.origin.y;
SELECT_Y = _view.frame.size.height * .9; SELECT_Y = _controlArea.size.height * .9 + _controlArea.origin.y;
} }
// For GCGamepad controls we move triggers, start, and select // For GCGamepad controls we move triggers, start, and select
@@ -229,7 +249,42 @@ static float L3_Y;
// Start with the default complex layout // Start with the default complex layout
[self setupComplexControls]; [self setupComplexControls];
// TODO START_X = _controlArea.size.width * .6 + _controlArea.origin.x;
START_Y = _controlArea.size.height * .9 + _controlArea.origin.y;
SELECT_X = _controlArea.size.width * .4 + _controlArea.origin.x;
SELECT_Y = _controlArea.size.height * .9 + _controlArea.origin.y;
L2_Y = _controlArea.size.height * .9 + _controlArea.origin.y;
L2_X = _controlArea.size.width * .1 + _controlArea.origin.x;
R2_Y = _controlArea.size.height * .9 + _controlArea.origin.y;
R2_X = _controlArea.size.width * .9 + _controlArea.origin.x;
if (_iPad) {
START_X = _controlArea.size.width * .75 + _controlArea.origin.x;
START_Y = _controlArea.size.height * .9 + _controlArea.origin.y;
SELECT_X = _controlArea.size.width * .25 + _controlArea.origin.x;
SELECT_Y = _controlArea.size.height * .9 + _controlArea.origin.y;
// The analog sticks are kept closer to the sides on iPad
LS_CENTER_X = _controlArea.size.width * .18 + _controlArea.origin.x;
LS_CENTER_Y = _controlArea.size.height * .75 + _controlArea.origin.y;
RS_CENTER_X = _controlArea.size.width * .82 + _controlArea.origin.x;
RS_CENTER_Y = _controlArea.size.height * .75 + _controlArea.origin.y;
} else {
START_X = _controlArea.size.width * .6 + _controlArea.origin.x;
START_Y = _controlArea.size.height * .9 + _controlArea.origin.y;
SELECT_X = _controlArea.size.width * .4 + _controlArea.origin.x;
SELECT_Y = _controlArea.size.height * .9 + _controlArea.origin.y;
LS_CENTER_X = _controlArea.size.width * .25 + _controlArea.origin.x;
LS_CENTER_Y = _controlArea.size.height * .75 + _controlArea.origin.y;
RS_CENTER_X = _controlArea.size.width * .75 + _controlArea.origin.x;
RS_CENTER_Y = _controlArea.size.height * .75 + _controlArea.origin.y;
}
} }
// For simple controls we move the triggers and buttons to the bottom // For simple controls we move the triggers and buttons to the bottom
@@ -237,46 +292,59 @@ static float L3_Y;
// Start with the default complex layout // Start with the default complex layout
[self setupComplexControls]; [self setupComplexControls];
START_Y = _view.frame.size.height * .9; START_X = _controlArea.size.width * .6 + _controlArea.origin.x;
SELECT_Y = _view.frame.size.height * .9; START_Y = _controlArea.size.height * .9 + _controlArea.origin.y;
SELECT_X = _controlArea.size.width * .4 + _controlArea.origin.x;
SELECT_Y = _controlArea.size.height * .9 + _controlArea.origin.y;
L2_Y = _controlArea.size.height * .9 + _controlArea.origin.y;
L2_X = _controlArea.size.width * .1 + _controlArea.origin.x;
L1_Y = _view.frame.size.height * .7; R2_Y = _controlArea.size.height * .9 + _controlArea.origin.y;
L2_Y = _view.frame.size.height * .9; R2_X = _controlArea.size.width * .9 + _controlArea.origin.x;
L2_X = _view.frame.size.width * .1;
R1_Y = _view.frame.size.height * .7;
R2_Y = _view.frame.size.height * .9;
R2_X = _view.frame.size.width * .9;
} }
- (void) setupComplexControls - (void) setupComplexControls
{ {
D_PAD_CENTER_X = _view.frame.size.width * .15; D_PAD_CENTER_X = _controlArea.size.width * .1 + _controlArea.origin.x;
D_PAD_CENTER_Y = _view.frame.size.height * .55; D_PAD_CENTER_Y = _controlArea.size.height * .60 + _controlArea.origin.y;
BUTTON_CENTER_X = _view.frame.size.width * .85; BUTTON_CENTER_X = _controlArea.size.width * .9 + _controlArea.origin.x;
BUTTON_CENTER_Y = _view.frame.size.height * .55; BUTTON_CENTER_Y = _controlArea.size.height * .60 + _controlArea.origin.y;
LS_CENTER_X = _view.frame.size.width * .35; if (_iPad)
LS_CENTER_Y = _view.frame.size.height * .75; {
RS_CENTER_X = _view.frame.size.width * .65; // The analog sticks are kept closer to the sides on iPad
RS_CENTER_Y = _view.frame.size.height * .75; LS_CENTER_X = _controlArea.size.width * .22 + _controlArea.origin.x;
LS_CENTER_Y = _controlArea.size.height * .75 + _controlArea.origin.y;
RS_CENTER_X = _controlArea.size.width * .77 + _controlArea.origin.x;
RS_CENTER_Y = _controlArea.size.height * .75 + _controlArea.origin.y;
}
else
{
LS_CENTER_X = _controlArea.size.width * .35 + _controlArea.origin.x;
LS_CENTER_Y = _controlArea.size.height * .75 + _controlArea.origin.y;
RS_CENTER_X = _controlArea.size.width * .65 + _controlArea.origin.x;
RS_CENTER_Y = _controlArea.size.height * .75 + _controlArea.origin.y;
}
START_X = _view.frame.size.width * .6; START_X = _controlArea.size.width * .9 + _controlArea.origin.x;
START_Y = _view.frame.size.height * .1; START_Y = _controlArea.size.height * .9 + _controlArea.origin.y;
SELECT_X = _view.frame.size.width * .4; SELECT_X = _controlArea.size.width * .1 + _controlArea.origin.x;
SELECT_Y = _view.frame.size.height * .1; SELECT_Y = _controlArea.size.height * .9 + _controlArea.origin.y;
L1_X = _view.frame.size.width * .15; L1_X = _controlArea.size.width * .1 + _controlArea.origin.x;
L1_Y = _view.frame.size.height * .27; L1_Y = _controlArea.size.height * .27 + _controlArea.origin.y;
L2_X = _view.frame.size.width * .15; L2_X = _controlArea.size.width * .1 + _controlArea.origin.x;
L2_Y = _view.frame.size.height * .1; L2_Y = _controlArea.size.height * .1 + _controlArea.origin.y;
R1_X = _view.frame.size.width * .85; R1_X = _controlArea.size.width * .9 + _controlArea.origin.x;
R1_Y = _view.frame.size.height * .27; R1_Y = _controlArea.size.height * .27 + _controlArea.origin.y;
R2_X = _view.frame.size.width * .85; R2_X = _controlArea.size.width * .9 + _controlArea.origin.x;
R2_Y = _view.frame.size.height * .1; R2_Y = _controlArea.size.height * .1 + _controlArea.origin.y;
L3_X = _view.frame.size.width * .25; L3_X = _controlArea.size.width * .25 + _controlArea.origin.x;
L3_Y = _view.frame.size.height * .75; L3_Y = _controlArea.size.height * .75 + _controlArea.origin.y;
R3_X = _view.frame.size.width * .75; R3_X = _controlArea.size.width * .75 + _controlArea.origin.x;
R3_Y = _view.frame.size.height * .75; R3_Y = _controlArea.size.height * .75 + _controlArea.origin.y;
} }
- (void) drawButtons { - (void) drawButtons {
@@ -285,43 +353,43 @@ static float L3_Y;
_aButton.contents = (id) aButtonImage.CGImage; _aButton.contents = (id) aButtonImage.CGImage;
_aButton.frame = CGRectMake(BUTTON_CENTER_X - aButtonImage.size.width / 2, BUTTON_CENTER_Y + BUTTON_DIST, aButtonImage.size.width, aButtonImage.size.height); _aButton.frame = CGRectMake(BUTTON_CENTER_X - aButtonImage.size.width / 2, BUTTON_CENTER_Y + BUTTON_DIST, aButtonImage.size.width, aButtonImage.size.height);
[_view.layer addSublayer:_aButton]; [_view.layer addSublayer:_aButton];
// create B button // create B button
UIImage* bButtonImage = [UIImage imageNamed:@"BButton"]; UIImage* bButtonImage = [UIImage imageNamed:@"BButton"];
_bButton.frame = CGRectMake(BUTTON_CENTER_X + BUTTON_DIST, BUTTON_CENTER_Y - bButtonImage.size.height / 2, bButtonImage.size.width, bButtonImage.size.height); _bButton.frame = CGRectMake(BUTTON_CENTER_X + BUTTON_DIST, BUTTON_CENTER_Y - bButtonImage.size.height / 2, bButtonImage.size.width, bButtonImage.size.height);
_bButton.contents = (id) bButtonImage.CGImage; _bButton.contents = (id) bButtonImage.CGImage;
[_view.layer addSublayer:_bButton]; [_view.layer addSublayer:_bButton];
// create X Button // create X Button
UIImage* xButtonImage = [UIImage imageNamed:@"XButton"]; UIImage* xButtonImage = [UIImage imageNamed:@"XButton"];
_xButton.frame = CGRectMake(BUTTON_CENTER_X - BUTTON_DIST - xButtonImage.size.width, BUTTON_CENTER_Y - xButtonImage.size.height/ 2, xButtonImage.size.width, xButtonImage.size.height); _xButton.frame = CGRectMake(BUTTON_CENTER_X - BUTTON_DIST - xButtonImage.size.width, BUTTON_CENTER_Y - xButtonImage.size.height/ 2, xButtonImage.size.width, xButtonImage.size.height);
_xButton.contents = (id) xButtonImage.CGImage; _xButton.contents = (id) xButtonImage.CGImage;
[_view.layer addSublayer:_xButton]; [_view.layer addSublayer:_xButton];
// create Y Button // create Y Button
UIImage* yButtonImage = [UIImage imageNamed:@"YButton"]; UIImage* yButtonImage = [UIImage imageNamed:@"YButton"];
_yButton.frame = CGRectMake(BUTTON_CENTER_X - yButtonImage.size.width / 2, BUTTON_CENTER_Y - BUTTON_DIST - yButtonImage.size.height, yButtonImage.size.width, yButtonImage.size.height); _yButton.frame = CGRectMake(BUTTON_CENTER_X - yButtonImage.size.width / 2, BUTTON_CENTER_Y - BUTTON_DIST - yButtonImage.size.height, yButtonImage.size.width, yButtonImage.size.height);
_yButton.contents = (id) yButtonImage.CGImage; _yButton.contents = (id) yButtonImage.CGImage;
[_view.layer addSublayer:_yButton]; [_view.layer addSublayer:_yButton];
// create Down button // create Down button
UIImage* downButtonImage = [UIImage imageNamed:@"DownButton"]; UIImage* downButtonImage = [UIImage imageNamed:@"DownButton"];
_downButton.frame = CGRectMake(D_PAD_CENTER_X - downButtonImage.size.width / 2, D_PAD_CENTER_Y + D_PAD_DIST, downButtonImage.size.width, downButtonImage.size.height); _downButton.frame = CGRectMake(D_PAD_CENTER_X - downButtonImage.size.width / 2, D_PAD_CENTER_Y + D_PAD_DIST, downButtonImage.size.width, downButtonImage.size.height);
_downButton.contents = (id) downButtonImage.CGImage; _downButton.contents = (id) downButtonImage.CGImage;
[_view.layer addSublayer:_downButton]; [_view.layer addSublayer:_downButton];
// create Right button // create Right button
UIImage* rightButtonImage = [UIImage imageNamed:@"RightButton"]; UIImage* rightButtonImage = [UIImage imageNamed:@"RightButton"];
_rightButton.frame = CGRectMake(D_PAD_CENTER_X + D_PAD_DIST, D_PAD_CENTER_Y - rightButtonImage.size.height / 2, rightButtonImage.size.width, rightButtonImage.size.height); _rightButton.frame = CGRectMake(D_PAD_CENTER_X + D_PAD_DIST, D_PAD_CENTER_Y - rightButtonImage.size.height / 2, rightButtonImage.size.width, rightButtonImage.size.height);
_rightButton.contents = (id) rightButtonImage.CGImage; _rightButton.contents = (id) rightButtonImage.CGImage;
[_view.layer addSublayer:_rightButton]; [_view.layer addSublayer:_rightButton];
// create Up button // create Up button
UIImage* upButtonImage = [UIImage imageNamed:@"UpButton"]; UIImage* upButtonImage = [UIImage imageNamed:@"UpButton"];
_upButton.frame = CGRectMake(D_PAD_CENTER_X - upButtonImage.size.width / 2, D_PAD_CENTER_Y - D_PAD_DIST - upButtonImage.size.height, upButtonImage.size.width, upButtonImage.size.height); _upButton.frame = CGRectMake(D_PAD_CENTER_X - upButtonImage.size.width / 2, D_PAD_CENTER_Y - D_PAD_DIST - upButtonImage.size.height, upButtonImage.size.width, upButtonImage.size.height);
_upButton.contents = (id) upButtonImage.CGImage; _upButton.contents = (id) upButtonImage.CGImage;
[_view.layer addSublayer:_upButton]; [_view.layer addSublayer:_upButton];
// create Left button // create Left button
UIImage* leftButtonImage = [UIImage imageNamed:@"LeftButton"]; UIImage* leftButtonImage = [UIImage imageNamed:@"LeftButton"];
_leftButton.frame = CGRectMake(D_PAD_CENTER_X - D_PAD_DIST - leftButtonImage.size.width, D_PAD_CENTER_Y - leftButtonImage.size.height / 2, leftButtonImage.size.width, leftButtonImage.size.height); _leftButton.frame = CGRectMake(D_PAD_CENTER_X - D_PAD_DIST - leftButtonImage.size.width, D_PAD_CENTER_Y - leftButtonImage.size.height / 2, leftButtonImage.size.width, leftButtonImage.size.height);
@@ -335,7 +403,7 @@ static float L3_Y;
_startButton.frame = CGRectMake(START_X - startButtonImage.size.width / 2, START_Y - startButtonImage.size.height / 2, startButtonImage.size.width, startButtonImage.size.height); _startButton.frame = CGRectMake(START_X - startButtonImage.size.width / 2, START_Y - startButtonImage.size.height / 2, startButtonImage.size.width, startButtonImage.size.height);
_startButton.contents = (id) startButtonImage.CGImage; _startButton.contents = (id) startButtonImage.CGImage;
[_view.layer addSublayer:_startButton]; [_view.layer addSublayer:_startButton];
// create Select button // create Select button
UIImage* selectButtonImage = [UIImage imageNamed:@"SelectButton"]; UIImage* selectButtonImage = [UIImage imageNamed:@"SelectButton"];
_selectButton.frame = CGRectMake(SELECT_X - selectButtonImage.size.width / 2, SELECT_Y - selectButtonImage.size.height / 2, selectButtonImage.size.width, selectButtonImage.size.height); _selectButton.frame = CGRectMake(SELECT_X - selectButtonImage.size.width / 2, SELECT_Y - selectButtonImage.size.height / 2, selectButtonImage.size.width, selectButtonImage.size.height);
@@ -382,7 +450,7 @@ static float L3_Y;
_leftStick.frame = CGRectMake(LS_CENTER_X - leftStickImage.size.width / 2, LS_CENTER_Y - leftStickImage.size.height / 2, leftStickImage.size.width, leftStickImage.size.height); _leftStick.frame = CGRectMake(LS_CENTER_X - leftStickImage.size.width / 2, LS_CENTER_Y - leftStickImage.size.height / 2, leftStickImage.size.width, leftStickImage.size.height);
_leftStick.contents = (id) leftStickImage.CGImage; _leftStick.contents = (id) leftStickImage.CGImage;
[_view.layer addSublayer:_leftStick]; [_view.layer addSublayer:_leftStick];
// create right analog stick // create right analog stick
UIImage* rightStickBgImage = [UIImage imageNamed:@"StickOuter"]; UIImage* rightStickBgImage = [UIImage imageNamed:@"StickOuter"];
_rightStickBackground.frame = CGRectMake(RS_CENTER_X - rightStickBgImage.size.width / 2, RS_CENTER_Y - rightStickBgImage.size.height / 2, rightStickBgImage.size.width, rightStickBgImage.size.height); _rightStickBackground.frame = CGRectMake(RS_CENTER_X - rightStickBgImage.size.width / 2, RS_CENTER_Y - rightStickBgImage.size.height / 2, rightStickBgImage.size.width, rightStickBgImage.size.height);
@@ -463,7 +531,7 @@ static float L3_Y;
float lsMaxY = LS_CENTER_Y + STICK_OUTER_SIZE / 2; float lsMaxY = LS_CENTER_Y + STICK_OUTER_SIZE / 2;
float lsMinX = LS_CENTER_X - STICK_OUTER_SIZE / 2; float lsMinX = LS_CENTER_X - STICK_OUTER_SIZE / 2;
float lsMinY = LS_CENTER_Y - STICK_OUTER_SIZE / 2; float lsMinY = LS_CENTER_Y - STICK_OUTER_SIZE / 2;
for (UITouch* touch in touches) { for (UITouch* touch in touches) {
CGPoint touchLocation = [touch locationInView:_view]; CGPoint touchLocation = [touch locationInView:_view];
float xLoc = touchLocation.x; float xLoc = touchLocation.x;
@@ -473,34 +541,34 @@ static float L3_Y;
if (xLoc < lsMinX) xLoc = lsMinX; if (xLoc < lsMinX) xLoc = lsMinX;
if (yLoc > lsMaxY) yLoc = lsMaxY; if (yLoc > lsMaxY) yLoc = lsMaxY;
if (yLoc < lsMinY) yLoc = lsMinY; if (yLoc < lsMinY) yLoc = lsMinY;
_leftStick.frame = CGRectMake(xLoc - STICK_INNER_SIZE / 2, yLoc - STICK_INNER_SIZE / 2, STICK_INNER_SIZE, STICK_INNER_SIZE); _leftStick.frame = CGRectMake(xLoc - STICK_INNER_SIZE / 2, yLoc - STICK_INNER_SIZE / 2, STICK_INNER_SIZE, STICK_INNER_SIZE);
float xStickVal = (xLoc - LS_CENTER_X) / (lsMaxX - LS_CENTER_X); float xStickVal = (xLoc - LS_CENTER_X) / (lsMaxX - LS_CENTER_X);
float yStickVal = (yLoc - LS_CENTER_Y) / (lsMaxY - LS_CENTER_Y); float yStickVal = (yLoc - LS_CENTER_Y) / (lsMaxY - LS_CENTER_Y);
if (fabsf(xStickVal) < STICK_DEAD_ZONE) xStickVal = 0; if (fabsf(xStickVal) < STICK_DEAD_ZONE) xStickVal = 0;
if (fabsf(yStickVal) < STICK_DEAD_ZONE) yStickVal = 0; if (fabsf(yStickVal) < STICK_DEAD_ZONE) yStickVal = 0;
[_controllerSupport updateLeftStick:_controller x:0x7FFE * xStickVal y:0x7FFE * -yStickVal]; [_controllerSupport updateLeftStick:_controller x:0x7FFE * xStickVal y:0x7FFE * -yStickVal];
updated = true; updated = true;
} else if (touch == _rsTouch) { } else if (touch == _rsTouch) {
if (xLoc > rsMaxX) xLoc = rsMaxX; if (xLoc > rsMaxX) xLoc = rsMaxX;
if (xLoc < rsMinX) xLoc = rsMinX; if (xLoc < rsMinX) xLoc = rsMinX;
if (yLoc > rsMaxY) yLoc = rsMaxY; if (yLoc > rsMaxY) yLoc = rsMaxY;
if (yLoc < rsMinY) yLoc = rsMinY; if (yLoc < rsMinY) yLoc = rsMinY;
_rightStick.frame = CGRectMake(xLoc - STICK_INNER_SIZE / 2, yLoc - STICK_INNER_SIZE / 2, STICK_INNER_SIZE, STICK_INNER_SIZE); _rightStick.frame = CGRectMake(xLoc - STICK_INNER_SIZE / 2, yLoc - STICK_INNER_SIZE / 2, STICK_INNER_SIZE, STICK_INNER_SIZE);
float xStickVal = (xLoc - RS_CENTER_X) / (rsMaxX - RS_CENTER_X); float xStickVal = (xLoc - RS_CENTER_X) / (rsMaxX - RS_CENTER_X);
float yStickVal = (yLoc - RS_CENTER_Y) / (rsMaxY - RS_CENTER_Y); float yStickVal = (yLoc - RS_CENTER_Y) / (rsMaxY - RS_CENTER_Y);
if (fabsf(xStickVal) < STICK_DEAD_ZONE) xStickVal = 0; if (fabsf(xStickVal) < STICK_DEAD_ZONE) xStickVal = 0;
if (fabsf(yStickVal) < STICK_DEAD_ZONE) yStickVal = 0; if (fabsf(yStickVal) < STICK_DEAD_ZONE) yStickVal = 0;
[_controllerSupport updateRightStick:_controller x:0x7FFE * xStickVal y:0x7FFE * -yStickVal]; [_controllerSupport updateRightStick:_controller x:0x7FFE * xStickVal y:0x7FFE * -yStickVal];
updated = true; updated = true;
} else if (touch == _aTouch) { } else if (touch == _aTouch) {
buttonTouch = true; buttonTouch = true;
@@ -535,11 +603,14 @@ static float L3_Y;
} else if (touch == _r3Touch) { } else if (touch == _r3Touch) {
buttonTouch = true; buttonTouch = true;
} }
if ([_deadTouches containsObject:touch]) {
updated = true;
}
} }
if (updated) { if (updated) {
[_controllerSupport updateFinished:_controller]; [_controllerSupport updateFinished:_controller];
} }
return updated || buttonTouch; return updated || buttonTouch;
} }
- (BOOL)handleTouchDownEvent:touches { - (BOOL)handleTouchDownEvent:touches {
@@ -653,6 +724,10 @@ static float L3_Y;
} else if ([_edge.presentationLayer hitTest:touchLocation]) { } else if ([_edge.presentationLayer hitTest:touchLocation]) {
_edgeTouch = touch; _edgeTouch = touch;
} }
if (!updated && !stickTouch && [self isInDeadZone:touch]) {
[_deadTouches addObject:touch];
updated = true;
}
} }
if (updated) { if (updated) {
[_controllerSupport updateFinished:_controller]; [_controllerSupport updateFinished:_controller];
@@ -744,15 +819,123 @@ static float L3_Y;
touched = true; touched = true;
} else if (touch == _edgeTouch) { } else if (touch == _edgeTouch) {
_edgeTouch = nil; _edgeTouch = nil;
if (![_edge.presentationLayer hitTest:[touch locationInView:_view]]) { if ([touch locationInView:_view].x >= _view.frame.size.width / 4) {
[_edgeDelegate edgeSwiped]; [_edgeDelegate edgeSwiped];
} }
} }
if ([_deadTouches containsObject:touch]) {
[_deadTouches removeObject:touch];
updated = true;
}
} }
if (updated) { if (updated) {
[_controllerSupport updateFinished:_controller]; [_controllerSupport updateFinished:_controller];
} }
return updated || touched; return updated || touched;
} }
- (BOOL) isInDeadZone:(UITouch*) touch {
switch (_level) {
case OnScreenControlsLevelFull:
return [self isDpadDeadZone:touch]
|| [self isAbxyDeadZone:touch]
|| [self isTriggerDeadZone:touch]
|| [self isBumperDeadZone:touch]
|| [self isStartSelectDeadZone:touch];
case OnScreenControlsLevelSimple:
return [self isTriggerDeadZone:touch]
|| [self isStartSelectDeadZone:touch]
|| [self isL3R3DeadZone:touch];
case OnScreenControlsLevelAutoGCExtendedGamepad:
return [self isL3R3DeadZone:touch]
|| [self isStartSelectDeadZone:touch];
case OnScreenControlsLevelAutoGCGamepad:
return [self isTriggerDeadZone:touch]
|| [self isStartSelectDeadZone:touch];
default:
return false;
}
}
- (BOOL) isDpadDeadZone:(UITouch*) touch {
return [self isDeadZone:touch
startX:_view.frame.origin.x
startY:_upButton.frame.origin.y
endX:_rightButton.frame.origin.x + _rightButton.frame.size.width
endY:_view.frame.origin.y + _view.frame.size.height];
}
- (BOOL) isAbxyDeadZone:(UITouch*) touch {
return [self isDeadZone:touch
startX:_xButton.frame.origin.x
startY:_yButton.frame.origin.y
endX:_view.frame.origin.x + _view.frame.size.width
endY:_view.frame.origin.y + _view.frame.size.height];
}
- (BOOL) isBumperDeadZone:(UITouch*) touch {
return [self isDeadZone:touch
startX:_view.frame.origin.x
startY:_l2Button.frame.origin.y + _l2Button.frame.size.height
endX:_l1Button.frame.origin.x + _l1Button.frame.size.width
endY:_upButton.frame.origin.y]
|| [self isDeadZone:touch
startX:_r2Button.frame.origin.x
startY:_r2Button.frame.origin.y + _r2Button.frame.size.height
endX:_view.frame.origin.x + _view.frame.size.width
endY:_yButton.frame.origin.y];
}
- (BOOL) isTriggerDeadZone:(UITouch*) touch {
return [self isDeadZone:touch
startX:_view.frame.origin.x
startY:_l2Button.frame.origin.y
endX:_l2Button.frame.origin.x + _l2Button.frame.size.width
endY:_view.frame.origin.y + _view.frame.size.height]
|| [self isDeadZone:touch
startX:_r2Button.frame.origin.x
startY:_r2Button.frame.origin.y
endX:_view.frame.origin.x + _view.frame.size.width
endY:_view.frame.origin.y + _view.frame.size.height];
}
- (BOOL) isL3R3DeadZone:(UITouch*) touch {
return [self isDeadZone:touch
startX:_view.frame.origin.x
startY:_l3Button.frame.origin.y
endX:_view.frame.origin.x
endY:_view.frame.origin.y + _view.frame.size.height]
|| [self isDeadZone:touch
startX:_r3Button.frame.origin.x
startY:_r3Button.frame.origin.y
endX:_view.frame.origin.x + _view.frame.size.width
endY:_view.frame.origin.y + _view.frame.size.height];
}
- (BOOL) isStartSelectDeadZone:(UITouch*) touch {
return [self isDeadZone:touch
startX:_startButton.frame.origin.x
startY:_startButton.frame.origin.y
endX:_view.frame.origin.x + _view.frame.size.width
endY:_view.frame.origin.y + _view.frame.size.height]
|| [self isDeadZone:touch
startX:_view.frame.origin.x
startY:_selectButton.frame.origin.y
endX:_selectButton.frame.origin.x + _selectButton.frame.size.width
endY:_view.frame.origin.y + _view.frame.size.height];
}
- (BOOL) isDeadZone:(UITouch*) touch startX:(float)deadZoneStartX startY:(float)deadZoneStartY endX:(float)deadZoneEndX endY:(float)deadZoneEndY {
deadZoneStartX -= DEAD_ZONE_PADDING;
deadZoneStartY -= DEAD_ZONE_PADDING;
deadZoneEndX += DEAD_ZONE_PADDING;
deadZoneEndY += DEAD_ZONE_PADDING;
CGPoint touchLocation = [touch locationInView:_view];
return (touchLocation.x > deadZoneStartX && touchLocation.x < deadZoneEndX
&& touchLocation.y > deadZoneStartY && touchLocation.y < deadZoneEndY);
}
@end @end
+15 -14
View File
@@ -32,7 +32,7 @@
- (void) setupOnScreenControls:(ControllerSupport*)controllerSupport swipeDelegate:(id<EdgeDetectionDelegate>)swipeDelegate { - (void) setupOnScreenControls:(ControllerSupport*)controllerSupport swipeDelegate:(id<EdgeDetectionDelegate>)swipeDelegate {
onScreenControls = [[OnScreenControls alloc] initWithView:self controllerSup:controllerSupport swipeDelegate:swipeDelegate]; onScreenControls = [[OnScreenControls alloc] initWithView:self controllerSup:controllerSupport swipeDelegate:swipeDelegate];
DataManager* dataMan = [[DataManager alloc] init]; DataManager* dataMan = [[DataManager alloc] init];
OnScreenControlsLevel level = (OnScreenControlsLevel)[[dataMan retrieveSettings].onscreenControls integerValue]; OnScreenControlsLevel level = (OnScreenControlsLevel)[[dataMan getSettings].onscreenControls integerValue];
if (level == OnScreenControlsLevelAuto) { if (level == OnScreenControlsLevelAuto) {
[controllerSupport initAutoOnScreenControlMode:onScreenControls]; [controllerSupport initAutoOnScreenControlMode:onScreenControls];
@@ -67,15 +67,16 @@
deltaX *= xDeltaFactor * screenFactor; deltaX *= xDeltaFactor * screenFactor;
deltaY *= yDeltaFactor * screenFactor; deltaY *= yDeltaFactor * screenFactor;
LiSendMouseMoveEvent(deltaX, deltaY); if (deltaX != 0 || deltaY != 0) {
LiSendMouseMoveEvent(deltaX, deltaY);
touchMoved = true; touchMoved = true;
touchLocation = currentLocation; touchLocation = currentLocation;
}
} }
} else if ([[event allTouches] count] == 2) { } else if ([[event allTouches] count] == 2) {
CGPoint firstLocation = [[[[event allTouches] allObjects] objectAtIndex:0] locationInView:self]; CGPoint firstLocation = [[[[event allTouches] allObjects] objectAtIndex:0] locationInView:self];
CGPoint secondLocation = [[[[event allTouches] allObjects] objectAtIndex:1] locationInView:self]; CGPoint secondLocation = [[[[event allTouches] allObjects] objectAtIndex:1] locationInView:self];
CGPoint avgLocation = CGPointMake((firstLocation.x + secondLocation.x) / 2, (firstLocation.y + secondLocation.y) / 2); CGPoint avgLocation = CGPointMake((firstLocation.x + secondLocation.x) / 2, (firstLocation.y + secondLocation.y) / 2);
if (touchLocation.y != avgLocation.y) { if (touchLocation.y != avgLocation.y) {
LiSendScrollEvent(avgLocation.y - touchLocation.y); LiSendScrollEvent(avgLocation.y - touchLocation.y);
@@ -93,23 +94,23 @@
if (!touchMoved) { if (!touchMoved) {
if ([[event allTouches] count] == 2) { if ([[event allTouches] count] == 2) {
Log(LOG_D, @"Sending right mouse button press"); Log(LOG_D, @"Sending right mouse button press");
LiSendMouseButtonEvent(BUTTON_ACTION_PRESS, BUTTON_RIGHT); LiSendMouseButtonEvent(BUTTON_ACTION_PRESS, BUTTON_RIGHT);
// Wait 100 ms to simulate a real button press // Wait 100 ms to simulate a real button press
usleep(100 * 1000); usleep(100 * 1000);
LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, BUTTON_RIGHT); LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, BUTTON_RIGHT);
} else { } else {
Log(LOG_D, @"Sending left mouse button press"); Log(LOG_D, @"Sending left mouse button press");
LiSendMouseButtonEvent(BUTTON_ACTION_PRESS, BUTTON_LEFT); LiSendMouseButtonEvent(BUTTON_ACTION_PRESS, BUTTON_LEFT);
// Wait 100 ms to simulate a real button press // Wait 100 ms to simulate a real button press
usleep(100 * 1000); usleep(100 * 1000);
LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, BUTTON_LEFT); LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, BUTTON_LEFT);
} }
} }
+6 -4
View File
@@ -17,11 +17,11 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>0.9.2</string> <string>1.0.2</string>
<key>CFBundleSignature</key> <key>CFBundleSignature</key>
<string>????</string> <string>????</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>2</string> <string>1</string>
<key>LSApplicationCategoryType</key> <key>LSApplicationCategoryType</key>
<string>public.app-category.games</string> <string>public.app-category.games</string>
<key>LSRequiresIPhoneOS</key> <key>LSRequiresIPhoneOS</key>
@@ -50,9 +50,9 @@
<array> <array>
<string>armv7</string> <string>armv7</string>
</array> </array>
<key>UIStatusBarHidden</key> <key>UIRequiresFullScreen</key>
<true/> <true/>
<key>UIStatusBarHidden~ipad</key> <key>UIStatusBarHidden</key>
<true/> <true/>
<key>UISupportedInterfaceOrientations</key> <key>UISupportedInterfaceOrientations</key>
<array> <array>
@@ -64,5 +64,7 @@
<string>UIInterfaceOrientationLandscapeLeft</string> <string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string> <string>UIInterfaceOrientationLandscapeRight</string>
</array> </array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
</dict> </dict>
</plist> </plist>
@@ -3,6 +3,6 @@
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>_XCCurrentVersionName</key> <key>_XCCurrentVersionName</key>
<string>Moonlight v1.0.xcdatamodel</string> <string>Moonlight v1.0-2.xcdatamodel</string>
</dict> </dict>
</plist> </plist>
@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="9057" systemVersion="15B42" minimumToolsVersion="Automatic">
<entity name="App" representedClassName="App" syncable="YES">
<attribute name="id" attributeType="String" syncable="YES"/>
<attribute name="image" optional="YES" attributeType="Binary" allowsExternalBinaryDataStorage="YES" syncable="YES"/>
<attribute name="name" optional="YES" attributeType="String" syncable="YES"/>
<relationship name="host" maxCount="1" deletionRule="Nullify" destinationEntity="Host" inverseName="appList" inverseEntity="Host" syncable="YES"/>
</entity>
<entity name="Host" representedClassName="Host" syncable="YES">
<attribute name="address" attributeType="String" syncable="YES"/>
<attribute name="externalAddress" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="localAddress" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="mac" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="name" attributeType="String" syncable="YES"/>
<attribute name="pairState" optional="YES" attributeType="Integer 32" defaultValueString="0" syncable="YES"/>
<attribute name="uuid" optional="YES" attributeType="String" syncable="YES"/>
<relationship name="appList" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="App" inverseName="host" inverseEntity="App" syncable="YES"/>
</entity>
<entity name="Settings" representedClassName="Settings" syncable="YES">
<attribute name="bitrate" attributeType="Integer 32" defaultValueString="10000" syncable="YES"/>
<attribute name="framerate" attributeType="Integer 32" defaultValueString="60" syncable="YES"/>
<attribute name="height" attributeType="Integer 32" defaultValueString="720" syncable="YES"/>
<attribute name="onscreenControls" attributeType="Integer 32" defaultValueString="1" syncable="YES"/>
<attribute name="uniqueId" attributeType="String" syncable="YES"/>
<attribute name="width" attributeType="Integer 32" defaultValueString="1280" syncable="YES"/>
</entity>
<elements>
<element name="App" positionX="0" positionY="54" width="128" height="105"/>
<element name="Host" positionX="0" positionY="0" width="128" height="163"/>
<element name="Settings" positionX="0" positionY="0" width="128" height="135"/>
</elements>
</model>
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="8118.17" systemVersion="15A204h" minimumToolsVersion="Automatic"> <model userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="9057" systemVersion="15B42" minimumToolsVersion="Automatic">
<entity name="App" representedClassName="App" syncable="YES"> <entity name="App" representedClassName="App" syncable="YES">
<attribute name="id" attributeType="String" syncable="YES"/> <attribute name="id" attributeType="String" syncable="YES"/>
<attribute name="image" optional="YES" attributeType="Binary" allowsExternalBinaryDataStorage="YES" syncable="YES"/> <attribute name="image" optional="YES" attributeType="Binary" allowsExternalBinaryDataStorage="YES" syncable="YES"/>
@@ -21,11 +21,12 @@
<attribute name="framerate" attributeType="Integer 32" defaultValueString="60" syncable="YES"/> <attribute name="framerate" attributeType="Integer 32" defaultValueString="60" syncable="YES"/>
<attribute name="height" attributeType="Integer 32" defaultValueString="720" syncable="YES"/> <attribute name="height" attributeType="Integer 32" defaultValueString="720" syncable="YES"/>
<attribute name="onscreenControls" attributeType="Integer 32" defaultValueString="1" syncable="YES"/> <attribute name="onscreenControls" attributeType="Integer 32" defaultValueString="1" syncable="YES"/>
<attribute name="uniqueId" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="width" attributeType="Integer 32" defaultValueString="1280" syncable="YES"/> <attribute name="width" attributeType="Integer 32" defaultValueString="1280" syncable="YES"/>
</entity> </entity>
<elements> <elements>
<element name="App" positionX="0" positionY="54" width="128" height="105"/> <element name="App" positionX="0" positionY="54" width="128" height="105"/>
<element name="Host" positionX="0" positionY="0" width="128" height="163"/> <element name="Host" positionX="0" positionY="0" width="128" height="163"/>
<element name="Settings" positionX="0" positionY="0" width="128" height="120"/> <element name="Settings" positionX="0" positionY="0" width="128" height="135"/>
</elements> </elements>
</model> </model>
+4 -4
View File
@@ -7,20 +7,20 @@
// //
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
#import "App.h" #import "TemporaryApp.h"
#import "HttpManager.h" #import "HttpManager.h"
#import "Host.h" #import "TemporaryHost.h"
@protocol AppAssetCallback <NSObject> @protocol AppAssetCallback <NSObject>
- (void) receivedAssetForApp:(App*)app; - (void) receivedAssetForApp:(TemporaryApp*)app;
@end @end
@interface AppAssetManager : NSObject @interface AppAssetManager : NSObject
- (id) initWithCallback:(id<AppAssetCallback>)callback; - (id) initWithCallback:(id<AppAssetCallback>)callback;
- (void) retrieveAssetsFromHost:(Host*)host; - (void) retrieveAssetsFromHost:(TemporaryHost*)host;
- (void) stopRetrieving; - (void) stopRetrieving;
@end @end
+3 -3
View File
@@ -28,8 +28,8 @@ static const int MAX_REQUEST_COUNT = 4;
return self; return self;
} }
- (void) retrieveAssetsFromHost:(Host*)host { - (void) retrieveAssetsFromHost:(TemporaryHost*)host {
for (App* app in host.appList) { for (TemporaryApp* app in host.appList) {
if (app.image == nil) { if (app.image == nil) {
AppAssetRetriever* retriever = [[AppAssetRetriever alloc] init]; AppAssetRetriever* retriever = [[AppAssetRetriever alloc] init];
retriever.app = app; retriever.app = app;
@@ -45,7 +45,7 @@ static const int MAX_REQUEST_COUNT = 4;
[_opQueue cancelAllOperations]; [_opQueue cancelAllOperations];
} }
- (void) sendCallBackForApp:(App*)app { - (void) sendCallBackForApp:(TemporaryApp*)app {
[_callback receivedAssetForApp:app]; [_callback receivedAssetForApp:app];
} }
+4 -4
View File
@@ -7,14 +7,14 @@
// //
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
#import "Host.h" #import "TemporaryHost.h"
#import "App.h" #import "TemporaryApp.h"
#import "AppAssetManager.h" #import "AppAssetManager.h"
@interface AppAssetRetriever : NSOperation @interface AppAssetRetriever : NSOperation
@property (nonatomic) Host* host; @property (nonatomic) TemporaryHost* host;
@property (nonatomic) App* app; @property (nonatomic) TemporaryApp* app;
@property (nonatomic) id<AppAssetCallback> callback; @property (nonatomic) id<AppAssetCallback> callback;
@end @end
+3 -2
View File
@@ -11,6 +11,7 @@
#import "CryptoManager.h" #import "CryptoManager.h"
#import "AppAssetResponse.h" #import "AppAssetResponse.h"
#import "HttpRequest.h" #import "HttpRequest.h"
#import "IdManager.h"
@implementation AppAssetRetriever @implementation AppAssetRetriever
static const double RETRY_DELAY = 2; // seconds static const double RETRY_DELAY = 2; // seconds
@@ -21,7 +22,7 @@ static const int MAX_ATTEMPTS = 5;
int attempts = 0; int attempts = 0;
while (![self isCancelled] && appImage == nil && attempts++ < MAX_ATTEMPTS) { while (![self isCancelled] && appImage == nil && attempts++ < MAX_ATTEMPTS) {
HttpManager* hMan = [[HttpManager alloc] initWithHost:_host.activeAddress uniqueId:[CryptoManager getUniqueID] deviceName:deviceName cert:[CryptoManager readCertFromFile]]; HttpManager* hMan = [[HttpManager alloc] initWithHost:_host.activeAddress uniqueId:[IdManager getUniqueId] deviceName:deviceName cert:[CryptoManager readCertFromFile]];
AppAssetResponse* appAssetResp = [[AppAssetResponse alloc] init]; AppAssetResponse* appAssetResp = [[AppAssetResponse alloc] init];
[hMan executeRequestSynchronously:[HttpRequest requestForResponse:appAssetResp withUrlRequest:[hMan newAppAssetRequestWithAppId:self.app.id]]]; [hMan executeRequestSynchronously:[HttpRequest requestForResponse:appAssetResp withUrlRequest:[hMan newAppAssetRequestWithAppId:self.app.id]]];
@@ -35,7 +36,7 @@ static const int MAX_ATTEMPTS = 5;
[self performSelectorOnMainThread:@selector(sendCallbackForApp:) withObject:self.app waitUntilDone:NO]; [self performSelectorOnMainThread:@selector(sendCallbackForApp:) withObject:self.app waitUntilDone:NO];
} }
- (void) sendCallbackForApp:(App*)app { - (void) sendCallbackForApp:(TemporaryApp*)app {
[self.callback receivedAssetForApp:app]; [self.callback receivedAssetForApp:app];
} }
+1 -1
View File
@@ -11,7 +11,7 @@
@interface AppListResponse : NSObject <Response> @interface AppListResponse : NSObject <Response>
- (void)populateWithData:(NSData *)data; - (void)populateWithData:(NSData *)data;
- (NSArray*) getAppList; - (NSSet*) getAppList;
- (BOOL) isStatusOk; - (BOOL) isStatusOk;
@end @end
+5 -6
View File
@@ -7,12 +7,12 @@
// //
#import "AppListResponse.h" #import "AppListResponse.h"
#import "App.h" #import "TemporaryApp.h"
#import "DataManager.h" #import "DataManager.h"
#import <libxml2/libxml/xmlreader.h> #import <libxml2/libxml/xmlreader.h>
@implementation AppListResponse { @implementation AppListResponse {
NSMutableArray* _appList; NSMutableSet* _appList;
} }
@synthesize data, statusCode, statusMessage; @synthesize data, statusCode, statusMessage;
@@ -23,7 +23,7 @@ static const char* TAG_APP_IS_RUNNING = "IsRunning";
- (void)populateWithData:(NSData *)xml { - (void)populateWithData:(NSData *)xml {
self.data = xml; self.data = xml;
_appList = [[NSMutableArray alloc] init]; _appList = [[NSMutableSet alloc] init];
[self parseData]; [self parseData];
} }
@@ -60,7 +60,6 @@ static const char* TAG_APP_IS_RUNNING = "IsRunning";
self.statusMessage = statusMsg; self.statusMessage = statusMsg;
node = node->children; node = node->children;
DataManager* dataMan = [[DataManager alloc] init];
while (node != NULL) { while (node != NULL) {
//Log(LOG_D, @"node: %s", node->name); //Log(LOG_D, @"node: %s", node->name);
@@ -96,7 +95,7 @@ static const char* TAG_APP_IS_RUNNING = "IsRunning";
appInfoNode = appInfoNode->next; appInfoNode = appInfoNode->next;
} }
if (appId != nil) { if (appId != nil) {
App* app = [dataMan createApp]; TemporaryApp* app = [[TemporaryApp alloc] init];
app.name = appName; app.name = appName;
app.id = appId; app.id = appId;
app.isRunning = appIsRunning; app.isRunning = appIsRunning;
@@ -109,7 +108,7 @@ static const char* TAG_APP_IS_RUNNING = "IsRunning";
xmlFreeDoc(docPtr); xmlFreeDoc(docPtr);
} }
- (NSArray*) getAppList { - (NSSet*) getAppList {
return _appList; return _appList;
} }
+4 -4
View File
@@ -8,7 +8,7 @@
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
#import "MDNSManager.h" #import "MDNSManager.h"
#import "Host.h" #import "TemporaryHost.h"
@protocol DiscoveryCallback <NSObject> @protocol DiscoveryCallback <NSObject>
@@ -22,8 +22,8 @@
- (void) startDiscovery; - (void) startDiscovery;
- (void) stopDiscovery; - (void) stopDiscovery;
- (void) stopDiscoveryBlocking; - (void) stopDiscoveryBlocking;
- (BOOL) addHostToDiscovery:(Host*)host; - (BOOL) addHostToDiscovery:(TemporaryHost*)host;
- (void) removeHostFromDiscovery:(Host*)host; - (void) removeHostFromDiscovery:(TemporaryHost*)host;
- (void) discoverHost:(NSString*)hostAddress withCallback:(void (^)(Host*, NSString*))callback; - (void) discoverHost:(NSString*)hostAddress withCallback:(void (^)(TemporaryHost*, NSString*))callback;
@end @end
+23 -19
View File
@@ -13,10 +13,10 @@
#import "DataManager.h" #import "DataManager.h"
#import "DiscoveryWorker.h" #import "DiscoveryWorker.h"
#import "ServerInfoResponse.h" #import "ServerInfoResponse.h"
#import "IdManager.h"
@implementation DiscoveryManager { @implementation DiscoveryManager {
NSMutableArray* _hostQueue; NSMutableArray* _hostQueue;
NSMutableArray* _discoveredHosts;
id<DiscoveryCallback> _callback; id<DiscoveryCallback> _callback;
MDNSManager* _mdnsMan; MDNSManager* _mdnsMan;
NSOperationQueue* _opQueue; NSOperationQueue* _opQueue;
@@ -27,31 +27,38 @@
- (id)initWithHosts:(NSArray *)hosts andCallback:(id<DiscoveryCallback>)callback { - (id)initWithHosts:(NSArray *)hosts andCallback:(id<DiscoveryCallback>)callback {
self = [super init]; self = [super init];
_hostQueue = [NSMutableArray arrayWithArray:hosts];
// Using addHostToDiscovery ensures no duplicates
// will make it into the list from the database
_callback = callback; _callback = callback;
shouldDiscover = NO;
_hostQueue = [NSMutableArray array];
for (TemporaryHost* host in hosts)
{
[self addHostToDiscovery:host];
}
[_callback updateAllHosts:_hostQueue];
_opQueue = [[NSOperationQueue alloc] init]; _opQueue = [[NSOperationQueue alloc] init];
_mdnsMan = [[MDNSManager alloc] initWithCallback:self]; _mdnsMan = [[MDNSManager alloc] initWithCallback:self];
[CryptoManager generateKeyPairUsingSSl]; [CryptoManager generateKeyPairUsingSSl];
_uniqueId = [CryptoManager getUniqueID]; _uniqueId = [IdManager getUniqueId];
_cert = [CryptoManager readCertFromFile]; _cert = [CryptoManager readCertFromFile];
shouldDiscover = NO;
return self; return self;
} }
- (void) discoverHost:(NSString *)hostAddress withCallback:(void (^)(Host *, NSString*))callback { - (void) discoverHost:(NSString *)hostAddress withCallback:(void (^)(TemporaryHost *, NSString*))callback {
HttpManager* hMan = [[HttpManager alloc] initWithHost:hostAddress uniqueId:_uniqueId deviceName:deviceName cert:_cert]; HttpManager* hMan = [[HttpManager alloc] initWithHost:hostAddress uniqueId:_uniqueId deviceName:deviceName cert:_cert];
ServerInfoResponse* serverInfoResponse = [[ServerInfoResponse alloc] init]; ServerInfoResponse* serverInfoResponse = [[ServerInfoResponse alloc] init];
[hMan executeRequestSynchronously:[HttpRequest requestForResponse:serverInfoResponse withUrlRequest:[hMan newServerInfoRequest] fallbackError:401 fallbackRequest:[hMan newHttpServerInfoRequest]]]; [hMan executeRequestSynchronously:[HttpRequest requestForResponse:serverInfoResponse withUrlRequest:[hMan newServerInfoRequest] fallbackError:401 fallbackRequest:[hMan newHttpServerInfoRequest]]];
Host* host = nil; TemporaryHost* host = nil;
if ([serverInfoResponse isStatusOk]) { if ([serverInfoResponse isStatusOk]) {
DataManager* dataMan = [[DataManager alloc] init]; host = [[TemporaryHost alloc] init];
host = [dataMan createHost];
host.activeAddress = host.address = hostAddress; host.activeAddress = host.address = hostAddress;
host.online = YES; host.online = YES;
[serverInfoResponse populateHost:host]; [serverInfoResponse populateHost:host];
if (![self addHostToDiscovery:host]) { if (![self addHostToDiscovery:host]) {
[dataMan removeHost:host];
callback(nil, @"Host already added"); callback(nil, @"Host already added");
} else { } else {
callback(host, nil); callback(host, nil);
@@ -66,7 +73,7 @@
Log(LOG_I, @"Starting discovery"); Log(LOG_I, @"Starting discovery");
shouldDiscover = YES; shouldDiscover = YES;
[_mdnsMan searchForHosts]; [_mdnsMan searchForHosts];
for (Host* host in _hostQueue) { for (TemporaryHost* host in _hostQueue) {
[_opQueue addOperation:[self createWorkerForHost:host]]; [_opQueue addOperation:[self createWorkerForHost:host]];
} }
} }
@@ -87,7 +94,7 @@
Log(LOG_I, @"All discovery workers stopped"); Log(LOG_I, @"All discovery workers stopped");
} }
- (BOOL) addHostToDiscovery:(Host *)host { - (BOOL) addHostToDiscovery:(TemporaryHost *)host {
if (host.uuid.length > 0 && ![self isHostInDiscovery:host]) { if (host.uuid.length > 0 && ![self isHostInDiscovery:host]) {
[_hostQueue addObject:host]; [_hostQueue addObject:host];
if (shouldDiscover) { if (shouldDiscover) {
@@ -98,7 +105,7 @@
return NO; return NO;
} }
- (void) removeHostFromDiscovery:(Host *)host { - (void) removeHostFromDiscovery:(TemporaryHost *)host {
for (DiscoveryWorker* worker in [_opQueue operations]) { for (DiscoveryWorker* worker in [_opQueue operations]) {
if ([worker getHost] == host) { if ([worker getHost] == host) {
[worker cancel]; [worker cancel];
@@ -110,9 +117,8 @@
// Override from MDNSCallback // Override from MDNSCallback
- (void)updateHosts:(NSArray *)hosts { - (void)updateHosts:(NSArray *)hosts {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
DataManager* dataMan = [[DataManager alloc] init];
// Discover the hosts before adding to eliminate duplicates // Discover the hosts before adding to eliminate duplicates
for (Host* host in hosts) { for (TemporaryHost* host in hosts) {
Log(LOG_I, @"Found host through MDNS: %@:", host.name); Log(LOG_I, @"Found host through MDNS: %@:", host.name);
// Since this is on a background thread, we do not need to use the opQueue // Since this is on a background thread, we do not need to use the opQueue
DiscoveryWorker* worker = (DiscoveryWorker*)[self createWorkerForHost:host]; DiscoveryWorker* worker = (DiscoveryWorker*)[self createWorkerForHost:host];
@@ -122,16 +128,14 @@
[_callback updateAllHosts:_hostQueue]; [_callback updateAllHosts:_hostQueue];
} else { } else {
Log(LOG_I, @"Not adding host to discovery: %@", host.name); Log(LOG_I, @"Not adding host to discovery: %@", host.name);
[dataMan removeHost:host];
} }
} }
[dataMan saveData];
}); });
} }
- (BOOL) isHostInDiscovery:(Host*)host { - (BOOL) isHostInDiscovery:(TemporaryHost*)host {
for (int i = 0; i < _hostQueue.count; i++) { for (int i = 0; i < _hostQueue.count; i++) {
Host* discoveredHost = [_hostQueue objectAtIndex:i]; TemporaryHost* discoveredHost = [_hostQueue objectAtIndex:i];
if (discoveredHost.uuid.length > 0 && [discoveredHost.uuid isEqualToString:host.uuid]) { if (discoveredHost.uuid.length > 0 && [discoveredHost.uuid isEqualToString:host.uuid]) {
return YES; return YES;
} }
@@ -139,7 +143,7 @@
return NO; return NO;
} }
- (NSOperation*) createWorkerForHost:(Host*)host { - (NSOperation*) createWorkerForHost:(TemporaryHost*)host {
DiscoveryWorker* worker = [[DiscoveryWorker alloc] initWithHost:host uniqueId:_uniqueId cert:_cert]; DiscoveryWorker* worker = [[DiscoveryWorker alloc] initWithHost:host uniqueId:_uniqueId cert:_cert];
return worker; return worker;
} }
+3 -3
View File
@@ -7,12 +7,12 @@
// //
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
#import "Host.h" #import "TemporaryHost.h"
@interface DiscoveryWorker : NSOperation @interface DiscoveryWorker : NSOperation
- (id) initWithHost:(Host*)host uniqueId:(NSString*)uniqueId cert:(NSData*)cert; - (id) initWithHost:(TemporaryHost*)host uniqueId:(NSString*)uniqueId cert:(NSData*)cert;
- (void) discoverHost; - (void) discoverHost;
- (Host*) getHost; - (TemporaryHost*) getHost;
@end @end
+30 -13
View File
@@ -11,16 +11,17 @@
#import "HttpManager.h" #import "HttpManager.h"
#import "ServerInfoResponse.h" #import "ServerInfoResponse.h"
#import "HttpRequest.h" #import "HttpRequest.h"
#import "DataManager.h"
@implementation DiscoveryWorker { @implementation DiscoveryWorker {
Host* _host; TemporaryHost* _host;
NSString* _uniqueId; NSString* _uniqueId;
NSData* _cert; NSData* _cert;
} }
static const float POLL_RATE = 2.0f; // Poll every 2 seconds static const float POLL_RATE = 2.0f; // Poll every 2 seconds
- (id) initWithHost:(Host*)host uniqueId:(NSString*)uniqueId cert:(NSData*)cert { - (id) initWithHost:(TemporaryHost*)host uniqueId:(NSString*)uniqueId cert:(NSData*)cert {
self = [super init]; self = [super init];
_host = host; _host = host;
_uniqueId = uniqueId; _uniqueId = uniqueId;
@@ -28,7 +29,7 @@ static const float POLL_RATE = 2.0f; // Poll every 2 seconds
return self; return self;
} }
- (Host*) getHost { - (TemporaryHost*) getHost {
return _host; return _host;
} }
@@ -87,20 +88,37 @@ static const float POLL_RATE = 2.0f; // Poll every 2 seconds
Log(LOG_D, @"%@ has %d unique addresses", _host.name, [addresses count]); Log(LOG_D, @"%@ has %d unique addresses", _host.name, [addresses count]);
for (NSString *address in addresses) { // Give the PC 3 tries to respond before declaring it offline
if (self.cancelled) { for (int i = 0; i < 3; i++) {
// Get out without updating the status because for (NSString *address in addresses) {
// it might not have finished checking the various if (self.cancelled) {
// addresses // Get out without updating the status because
return; // it might not have finished checking the various
// addresses
return;
}
ServerInfoResponse* serverInfoResp = [self requestInfoAtAddress:address];
receivedResponse = [self checkResponse:serverInfoResp];
if (receivedResponse) {
[serverInfoResp populateHost:_host];
_host.activeAddress = address;
// Update the database using the response
DataManager *dataManager = [[DataManager alloc] init];
[dataManager updateHost:_host];
break;
}
} }
ServerInfoResponse* serverInfoResp = [self requestInfoAtAddress:address];
receivedResponse = [self checkResponse:serverInfoResp];
if (receivedResponse) { if (receivedResponse) {
_host.activeAddress = address; Log(LOG_I, @"Received serverinfo response on try %d", i);
break; break;
} }
else {
// Wait for one second then retry
[NSThread sleepForTimeInterval:1];
}
} }
_host.online = receivedResponse; _host.online = receivedResponse;
@@ -125,7 +143,6 @@ static const float POLL_RATE = 2.0f; // Poll every 2 seconds
if ([response isStatusOk]) { if ([response isStatusOk]) {
// If the response is from a different host then do not update this host // If the response is from a different host then do not update this host
if ((_host.uuid == nil || [[response getStringTag:TAG_UNIQUE_ID] isEqualToString:_host.uuid])) { if ((_host.uuid == nil || [[response getStringTag:TAG_UNIQUE_ID] isEqualToString:_host.uuid])) {
[response populateHost:_host];
return YES; return YES;
} else { } else {
Log(LOG_I, @"Received response from incorrect host: %@ expected: %@", [response getStringTag:TAG_UNIQUE_ID], _host.uuid); Log(LOG_I, @"Received response from incorrect host: %@ expected: %@", [response getStringTag:TAG_UNIQUE_ID], _host.uuid);
-1
View File
@@ -7,7 +7,6 @@
// //
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
#import "Host.h"
#import "HttpResponse.h" #import "HttpResponse.h"
#import "HttpRequest.h" #import "HttpRequest.h"
+3 -3
View File
@@ -9,7 +9,7 @@
#import "HttpManager.h" #import "HttpManager.h"
#import "HttpRequest.h" #import "HttpRequest.h"
#import "CryptoManager.h" #import "CryptoManager.h"
#import "App.h" #import "TemporaryApp.h"
#include <libxml2/libxml/xmlreader.h> #include <libxml2/libxml/xmlreader.h>
#include <string.h> #include <string.h>
@@ -100,8 +100,8 @@ static const NSString* HTTPS_PORT = @"47984";
NSURL* url = [[NSURL alloc] initWithString:urlString]; NSURL* url = [[NSURL alloc] initWithString:urlString];
NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:url]; NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:url];
if (normalTimeout) { if (normalTimeout) {
// Timeout the request after 5 seconds // Timeout the request after 7 seconds
[request setTimeoutInterval:5]; [request setTimeoutInterval:7];
} }
else { else {
// Timeout the request after 60 seconds // Timeout the request after 60 seconds
-1
View File
@@ -7,7 +7,6 @@
// //
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
#import "Host.h"
static NSString* TAG_STATUS_CODE = @"status_code"; static NSString* TAG_STATUS_CODE = @"status_code";
static NSString* TAG_STATUS_MESSAGE = @"status_message"; static NSString* TAG_STATUS_MESSAGE = @"status_message";
+1 -1
View File
@@ -7,7 +7,7 @@
// //
#import "HttpResponse.h" #import "HttpResponse.h"
#import "App.h" #import "TemporaryApp.h"
#import <libxml2/libxml/xmlreader.h> #import <libxml2/libxml/xmlreader.h>
@implementation HttpResponse { @implementation HttpResponse {
+2 -4
View File
@@ -7,8 +7,7 @@
// //
#import "MDNSManager.h" #import "MDNSManager.h"
#import "Host.h" #import "TemporaryHost.h"
#import "DataManager.h"
@implementation MDNSManager { @implementation MDNSManager {
NSNetServiceBrowser* mDNSBrowser; NSNetServiceBrowser* mDNSBrowser;
@@ -47,10 +46,9 @@ static NSString* NV_SERVICE_TYPE = @"_nvstream._tcp";
- (NSArray*) getFoundHosts { - (NSArray*) getFoundHosts {
NSMutableArray* hosts = [[NSMutableArray alloc] init]; NSMutableArray* hosts = [[NSMutableArray alloc] init];
DataManager* dataMan = [[DataManager alloc] init];
for (NSNetService* service in services) { for (NSNetService* service in services) {
if (service.hostName != nil) { if (service.hostName != nil) {
Host* host = [dataMan createHost]; TemporaryHost* host = [[TemporaryHost alloc] init];
host.activeAddress = host.address = service.hostName; host.activeAddress = host.address = service.hostName;
host.name = host.address; host.name = host.address;
[hosts addObject:host]; [hosts addObject:host];
+1 -1
View File
@@ -38,7 +38,7 @@
return; return;
} }
if ([serverInfoResp isStatusOk]) { if ([serverInfoResp isStatusOk]) {
if (![[serverInfoResp getStringTag:@"currentgame"] isEqual:@"0"]) { if (![[serverInfoResp getStringTag:@"state"] hasSuffix:@"_SERVER_AVAILABLE"]) {
[_callback pairFailed:@"You must stop streaming before attempting to pair."]; [_callback pairFailed:@"You must stop streaming before attempting to pair."];
} else if (![[serverInfoResp getStringTag:@"PairStatus"] isEqual:@"1"]) { } else if (![[serverInfoResp getStringTag:@"PairStatus"] isEqual:@"1"]) {
[self initiatePair]; [self initiatePair];
+2 -1
View File
@@ -7,6 +7,7 @@
// //
#import "HttpResponse.h" #import "HttpResponse.h"
#import "TemporaryHost.h"
#define TAG_HOSTNAME @"hostname" #define TAG_HOSTNAME @"hostname"
#define TAG_EXTERNAL_IP @"ExternalIP" #define TAG_EXTERNAL_IP @"ExternalIP"
@@ -18,7 +19,7 @@
@interface ServerInfoResponse : HttpResponse <Response> @interface ServerInfoResponse : HttpResponse <Response>
- (void) populateWithData:(NSData *)data; - (void) populateWithData:(NSData *)data;
- (void) populateHost:(Host*)host; - (void) populateHost:(TemporaryHost*)host;
@end @end
+1 -1
View File
@@ -17,7 +17,7 @@
[super parseData]; [super parseData];
} }
- (void) populateHost:(Host*)host { - (void) populateHost:(TemporaryHost*)host {
host.name = [[self getStringTag:TAG_HOSTNAME] trim]; host.name = [[self getStringTag:TAG_HOSTNAME] trim];
host.externalAddress = [[self getStringTag:TAG_EXTERNAL_IP] trim]; host.externalAddress = [[self getStringTag:TAG_EXTERNAL_IP] trim];
host.localAddress = [[self getStringTag:TAG_LOCAL_IP] trim]; host.localAddress = [[self getStringTag:TAG_LOCAL_IP] trim];
+2 -2
View File
@@ -7,10 +7,10 @@
// //
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
#import "Host.h" #import "TemporaryHost.h"
@interface WakeOnLanManager : NSObject @interface WakeOnLanManager : NSObject
+ (void) wakeHost:(Host*)host; + (void) wakeHost:(TemporaryHost*)host;
@end @end
+2 -2
View File
@@ -18,7 +18,7 @@
static const int numPorts = 5; static const int numPorts = 5;
static const int ports[numPorts] = {7, 9, 47998, 47999, 48000}; static const int ports[numPorts] = {7, 9, 47998, 47999, 48000};
+ (void) wakeHost:(Host*)host { + (void) wakeHost:(TemporaryHost*)host {
NSData* wolPayload = [WakeOnLanManager createPayload:host]; NSData* wolPayload = [WakeOnLanManager createPayload:host];
CFDataRef dataPayload = CFDataCreate(kCFAllocatorDefault, [wolPayload bytes], [wolPayload length]); CFDataRef dataPayload = CFDataCreate(kCFAllocatorDefault, [wolPayload bytes], [wolPayload length]);
CFSocketRef wolSocket = CFSocketCreate(kCFAllocatorDefault, PF_INET, SOCK_DGRAM, IPPROTO_UDP, 0, NULL, NULL); CFSocketRef wolSocket = CFSocketCreate(kCFAllocatorDefault, PF_INET, SOCK_DGRAM, IPPROTO_UDP, 0, NULL, NULL);
@@ -56,7 +56,7 @@ static const int ports[numPorts] = {7, 9, 47998, 47999, 48000};
CFRelease(dataPayload); CFRelease(dataPayload);
} }
+ (NSData*) createPayload:(Host*)host { + (NSData*) createPayload:(TemporaryHost*)host {
NSMutableData* payload = [[NSMutableData alloc] initWithCapacity:102]; NSMutableData* payload = [[NSMutableData alloc] initWithCapacity:102];
// 6 bytes of FF // 6 bytes of FF
+16 -7
View File
@@ -65,11 +65,16 @@ int DrSubmitDecodeUnit(PDECODE_UNIT decodeUnit)
return [renderer submitDecodeBuffer:data length:decodeUnit->fullLength]; return [renderer submitDecodeBuffer:data length:decodeUnit->fullLength];
} }
void ArInit(void) void ArInit(int audioConfiguration, POPUS_MULTISTREAM_CONFIGURATION opusConfig)
{ {
int err; int err;
opusDecoder = opus_decoder_create(48000, 2, &err); // We only support stereo for now
assert(audioConfiguration == AUDIO_CONFIGURATION_STEREO);
opusDecoder = opus_decoder_create(opusConfig->sampleRate,
opusConfig->channelCount,
&err);
audioLock = [[NSLock alloc] init]; audioLock = [[NSLock alloc] init];
@@ -77,9 +82,9 @@ void ArInit(void)
NSError *audioSessionError = nil; NSError *audioSessionError = nil;
AVAudioSession* audioSession = [AVAudioSession sharedInstance]; AVAudioSession* audioSession = [AVAudioSession sharedInstance];
[audioSession setPreferredSampleRate:48000.0 error:&audioSessionError]; [audioSession setPreferredSampleRate:opusConfig->sampleRate error:&audioSessionError];
[audioSession setCategory: AVAudioSessionCategoryPlayback error: &audioSessionError]; [audioSession setCategory: AVAudioSessionCategoryPlayback error: &audioSessionError];
[audioSession setPreferredOutputNumberOfChannels:2 error:&audioSessionError]; [audioSession setPreferredOutputNumberOfChannels:opusConfig->channelCount error:&audioSessionError];
[audioSession setPreferredIOBufferDuration:0.005 error:&audioSessionError]; [audioSession setPreferredIOBufferDuration:0.005 error:&audioSessionError];
[audioSession setActive: YES error: &audioSessionError]; [audioSession setActive: YES error: &audioSessionError];
@@ -99,11 +104,11 @@ void ArInit(void)
} }
AudioStreamBasicDescription audioFormat = {0}; AudioStreamBasicDescription audioFormat = {0};
audioFormat.mSampleRate = 48000; audioFormat.mSampleRate = opusConfig->sampleRate;
audioFormat.mBitsPerChannel = 16; audioFormat.mBitsPerChannel = 16;
audioFormat.mFormatID = kAudioFormatLinearPCM; audioFormat.mFormatID = kAudioFormatLinearPCM;
audioFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked; audioFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked;
audioFormat.mChannelsPerFrame = 2; audioFormat.mChannelsPerFrame = opusConfig->channelCount;
audioFormat.mBytesPerFrame = audioFormat.mChannelsPerFrame * (audioFormat.mBitsPerChannel / 8); audioFormat.mBytesPerFrame = audioFormat.mChannelsPerFrame * (audioFormat.mBitsPerChannel / 8);
audioFormat.mBytesPerPacket = audioFormat.mBytesPerFrame; audioFormat.mBytesPerPacket = audioFormat.mBytesPerFrame;
audioFormat.mFramesPerPacket = audioFormat.mBytesPerPacket / audioFormat.mBytesPerFrame; audioFormat.mFramesPerPacket = audioFormat.mBytesPerPacket / audioFormat.mBytesPerFrame;
@@ -280,6 +285,8 @@ void ClDisplayTransientMessage(char* message)
renderer = myRenderer; renderer = myRenderer;
_callbacks = callbacks; _callbacks = callbacks;
_serverMajorVersion = serverMajorVersion; _serverMajorVersion = serverMajorVersion;
LiInitializeStreamConfiguration(&_streamConfig);
_streamConfig.width = config.width; _streamConfig.width = config.width;
_streamConfig.height = config.height; _streamConfig.height = config.height;
_streamConfig.fps = config.frameRate; _streamConfig.fps = config.frameRate;
@@ -293,13 +300,15 @@ void ClDisplayTransientMessage(char* message)
int riKeyId = htonl(config.riKeyId); int riKeyId = htonl(config.riKeyId);
memcpy(_streamConfig.remoteInputAesIv, &riKeyId, sizeof(riKeyId)); memcpy(_streamConfig.remoteInputAesIv, &riKeyId, sizeof(riKeyId));
memset(&_drCallbacks, 0, sizeof(_drCallbacks)); LiInitializeVideoCallbacks(&_drCallbacks);
_drCallbacks.submitDecodeUnit = DrSubmitDecodeUnit; _drCallbacks.submitDecodeUnit = DrSubmitDecodeUnit;
LiInitializeAudioCallbacks(&_arCallbacks);
_arCallbacks.init = ArInit; _arCallbacks.init = ArInit;
_arCallbacks.cleanup = ArCleanup; _arCallbacks.cleanup = ArCleanup;
_arCallbacks.decodeAndPlaySample = ArDecodeAndPlaySample; _arCallbacks.decodeAndPlaySample = ArDecodeAndPlaySample;
LiInitializeConnectionCallbacks(&_clCallbacks);
_clCallbacks.stageStarting = ClStageStarting; _clCallbacks.stageStarting = ClStageStarting;
_clCallbacks.stageComplete = ClStageComplete; _clCallbacks.stageComplete = ClStageComplete;
_clCallbacks.stageFailed = ClStageFailed; _clCallbacks.stageFailed = ClStageFailed;
+5 -10
View File
@@ -15,6 +15,7 @@
#import "ServerInfoResponse.h" #import "ServerInfoResponse.h"
#import "HttpResponse.h" #import "HttpResponse.h"
#import "HttpRequest.h" #import "HttpRequest.h"
#import "IdManager.h"
@implementation StreamManager { @implementation StreamManager {
StreamConfiguration* _config; StreamConfiguration* _config;
@@ -36,7 +37,7 @@
- (void)main { - (void)main {
[CryptoManager generateKeyPairUsingSSl]; [CryptoManager generateKeyPairUsingSSl];
NSString* uniqueId = [CryptoManager getUniqueID]; NSString* uniqueId = [IdManager getUniqueId];
NSData* cert = [CryptoManager readCertFromFile]; NSData* cert = [CryptoManager readCertFromFile];
HttpManager* hMan = [[HttpManager alloc] initWithHost:_config.host HttpManager* hMan = [[HttpManager alloc] initWithHost:_config.host
@@ -47,11 +48,10 @@
ServerInfoResponse* serverInfoResp = [[ServerInfoResponse alloc] init]; ServerInfoResponse* serverInfoResp = [[ServerInfoResponse alloc] init];
[hMan executeRequestSynchronously:[HttpRequest requestForResponse:serverInfoResp withUrlRequest:[hMan newServerInfoRequest] [hMan executeRequestSynchronously:[HttpRequest requestForResponse:serverInfoResp withUrlRequest:[hMan newServerInfoRequest]
fallbackError:401 fallbackRequest:[hMan newHttpServerInfoRequest]]]; fallbackError:401 fallbackRequest:[hMan newHttpServerInfoRequest]]];
NSString* currentGame = [serverInfoResp getStringTag:@"currentgame"];
NSString* pairStatus = [serverInfoResp getStringTag:@"PairStatus"]; NSString* pairStatus = [serverInfoResp getStringTag:@"PairStatus"];
NSString* currentClient = [serverInfoResp getStringTag:@"CurrentClient"];
NSString* appversion = [serverInfoResp getStringTag:@"appversion"]; NSString* appversion = [serverInfoResp getStringTag:@"appversion"];
if (![serverInfoResp isStatusOk] || currentGame == NULL || pairStatus == NULL || appversion == NULL) { NSString* serverState = [serverInfoResp getStringTag:@"state"];
if (![serverInfoResp isStatusOk] || pairStatus == NULL || appversion == NULL || serverState == NULL) {
[_callbacks launchFailed:@"Failed to connect to PC"]; [_callbacks launchFailed:@"Failed to connect to PC"];
return; return;
} }
@@ -63,12 +63,7 @@
} }
// resumeApp and launchApp handle calling launchFailed // resumeApp and launchApp handle calling launchFailed
if (![currentGame isEqualToString:@"0"]) { if ([serverState hasSuffix:@"_SERVER_BUSY"]) {
if (![currentClient isEqualToString:@"1"]) {
// The server is streaming to someone else
[_callbacks launchFailed:@"There is another stream in progress"];
return;
}
// App already running, resume it // App already running, resume it
if (![self resumeApp:hMan]) { if (![self resumeApp:hMan]) {
return; return;
+3 -3
View File
@@ -7,17 +7,17 @@
// //
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
#import "App.h" #import "TemporaryApp.h"
@protocol AppCallback <NSObject> @protocol AppCallback <NSObject>
- (void) appClicked:(App*) app; - (void) appClicked:(TemporaryApp*) app;
@end @end
@interface UIAppView : UIView @interface UIAppView : UIView
- (id) initWithApp:(App*)app andCallback:(id<AppCallback>)callback; - (id) initWithApp:(TemporaryApp*)app cache:(NSCache*)cache andCallback:(id<AppCallback>)callback;
- (void) updateAppImage; - (void) updateAppImage;
@end @end
+42 -15
View File
@@ -9,40 +9,43 @@
#import "UIAppView.h" #import "UIAppView.h"
@implementation UIAppView { @implementation UIAppView {
App* _app; TemporaryApp* _app;
UIButton* _appButton; UIButton* _appButton;
UILabel* _appLabel; UILabel* _appLabel;
UIImageView* _appOverlay; UIImageView* _appOverlay;
NSCache* _artCache;
id<AppCallback> _callback; id<AppCallback> _callback;
} }
- (id) initWithApp:(App*)app andCallback:(id<AppCallback>)callback { static UIImage* noImage;
- (id) initWithApp:(TemporaryApp*)app cache:(NSCache*)cache andCallback:(id<AppCallback>)callback {
self = [super init]; self = [super init];
_app = app; _app = app;
_callback = callback; _callback = callback;
_artCache = cache;
// Cache the NoAppImage ourselves to avoid
// having to load it each time
if (noImage == nil) {
noImage = [UIImage imageNamed:@"NoAppImage"];
}
_appButton = [UIButton buttonWithType:UIButtonTypeCustom]; _appButton = [UIButton buttonWithType:UIButtonTypeCustom];
UIImage* noImage = [UIImage imageNamed:@"NoAppImage"];
[_appButton setBackgroundImage:noImage forState:UIControlStateNormal]; [_appButton setBackgroundImage:noImage forState:UIControlStateNormal];
[_appButton setContentEdgeInsets:UIEdgeInsetsMake(0, 4, 0, 4)]; [_appButton setContentEdgeInsets:UIEdgeInsetsMake(0, 4, 0, 4)];
[_appButton sizeToFit]; [_appButton sizeToFit];
[_appButton addTarget:self action:@selector(appClicked) forControlEvents:UIControlEventTouchUpInside]; [_appButton addTarget:self action:@selector(appClicked) forControlEvents:UIControlEventTouchUpInside];
_appOverlay = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"Play"]];
_appOverlay.layer.shadowColor = [UIColor blackColor].CGColor;
_appOverlay.layer.shadowOffset = CGSizeMake(0, 0);
_appOverlay.layer.shadowOpacity = 1;
_appOverlay.layer.shadowRadius = 2.0;
[_appOverlay setHidden: YES];
[self addSubview:_appButton]; [self addSubview:_appButton];
[self addSubview:_appOverlay];
[self sizeToFit]; [self sizeToFit];
_appButton.frame = CGRectMake(0, 0, noImage.size.width, noImage.size.height); _appButton.frame = CGRectMake(0, 0, noImage.size.width, noImage.size.height);
_appOverlay.frame = CGRectMake(0, 0, noImage.size.width / 2.f, noImage.size.height / 4.f);
self.frame = CGRectMake(0, 0, noImage.size.width, noImage.size.height); self.frame = CGRectMake(0, 0, noImage.size.width, noImage.size.height);
[_appOverlay setCenter:CGPointMake(self.frame.size.width/2, self.frame.size.height/6)];
// Rasterizing the cell layer increases rendering performance by quite a bit
self.layer.shouldRasterize = YES;
self.layer.rasterizationScale = [UIScreen mainScreen].scale;
return self; return self;
} }
@@ -52,13 +55,37 @@
} }
- (void) updateAppImage { - (void) updateAppImage {
[_appOverlay setHidden:!_app.isRunning]; if (_appOverlay != nil) {
[_appOverlay removeFromSuperview];
_appOverlay = nil;
}
if (_app.isRunning) {
// Only create the app overlay if needed
_appOverlay = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"Play"]];
_appOverlay.layer.shadowColor = [UIColor blackColor].CGColor;
_appOverlay.layer.shadowOffset = CGSizeMake(0, 0);
_appOverlay.layer.shadowOpacity = 1;
_appOverlay.layer.shadowRadius = 2.0;
[self addSubview:_appOverlay];
_appOverlay.frame = CGRectMake(0, 0, noImage.size.width / 2.f, noImage.size.height / 4.f);
[_appOverlay setCenter:CGPointMake(self.frame.size.width/2, self.frame.size.height/6)];
}
// TODO: Improve no-app image detection // TODO: Improve no-app image detection
BOOL noAppImage = false; BOOL noAppImage = false;
if (_app.image != nil) { if (_app.image != nil) {
UIImage* appImage = [UIImage imageWithData:_app.image]; // Load the decoded image from the cache
UIImage* appImage = [_artCache objectForKey:_app];
if (appImage == nil) {
// Not cached; we have to decode this now
appImage = [UIImage imageWithData:_app.image];
[_artCache setObject:appImage forKey:_app];
}
// This size of image might be blank image received from GameStream. // This size of image might be blank image received from GameStream.
if (!(appImage.size.width == 130.f && appImage.size.height == 180.f)) { if (!(appImage.size.width == 130.f && appImage.size.height == 180.f)) {
_appButton.frame = CGRectMake(0, 0, appImage.size.width / 2, appImage.size.height / 2); _appButton.frame = CGRectMake(0, 0, appImage.size.width / 2, appImage.size.height / 2);
+4 -4
View File
@@ -7,19 +7,19 @@
// //
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
#import "Host.h" #import "TemporaryHost.h"
@protocol HostCallback <NSObject> @protocol HostCallback <NSObject>
- (void) hostClicked:(Host*)host view:(UIView*)view; - (void) hostClicked:(TemporaryHost*)host view:(UIView*)view;
- (void) hostLongClicked:(Host*)host view:(UIView*)view; - (void) hostLongClicked:(TemporaryHost*)host view:(UIView*)view;
- (void) addHostClicked; - (void) addHostClicked;
@end @end
@interface UIComputerView : UIView @interface UIComputerView : UIView
- (id) initWithComputer:(Host*)host andCallback:(id<HostCallback>)callback; - (id) initWithComputer:(TemporaryHost*)host andCallback:(id<HostCallback>)callback;
- (id) initForAddWithCallback:(id<HostCallback>)callback; - (id) initForAddWithCallback:(id<HostCallback>)callback;
@end @end
+3 -3
View File
@@ -9,7 +9,7 @@
#import "UIComputerView.h" #import "UIComputerView.h"
@implementation UIComputerView { @implementation UIComputerView {
Host* _host; TemporaryHost* _host;
UIButton* _hostButton; UIButton* _hostButton;
UILabel* _hostLabel; UILabel* _hostLabel;
UILabel* _hostStatus; UILabel* _hostStatus;
@@ -72,7 +72,7 @@ static const int LABEL_DY = 20;
return self; return self;
} }
- (id) initWithComputer:(Host*)host andCallback:(id<HostCallback>)callback { - (id) initWithComputer:(TemporaryHost*)host andCallback:(id<HostCallback>)callback {
self = [self init]; self = [self init];
_host = host; _host = host;
_callback = callback; _callback = callback;
@@ -123,7 +123,7 @@ static const int LABEL_DY = 20;
self.frame = CGRectMake(x, y, width, height); self.frame = CGRectMake(x, y, width, height);
} }
- (void) updateContentsForHost:(Host*)host { - (void) updateContentsForHost:(TemporaryHost*)host {
_hostLabel.text = _host.name; _hostLabel.text = _host.name;
_hostLabel.textColor = [UIColor whiteColor]; _hostLabel.textColor = [UIColor whiteColor];
[_hostLabel sizeToFit]; [_hostLabel sizeToFit];
@@ -5,6 +5,8 @@
// Copyright (c) 2014 Moonlight Stream. All rights reserved. // Copyright (c) 2014 Moonlight Stream. All rights reserved.
// //
@import ImageIO;
#import "MainFrameViewController.h" #import "MainFrameViewController.h"
#import "CryptoManager.h" #import "CryptoManager.h"
#import "HttpManager.h" #import "HttpManager.h"
@@ -13,19 +15,21 @@
#import "Utils.h" #import "Utils.h"
#import "UIComputerView.h" #import "UIComputerView.h"
#import "UIAppView.h" #import "UIAppView.h"
#import "App.h"
#import "SettingsViewController.h" #import "SettingsViewController.h"
#import "DataManager.h" #import "DataManager.h"
#import "Settings.h" #import "TemporarySettings.h"
#import "WakeOnLanManager.h" #import "WakeOnLanManager.h"
#import "AppListResponse.h" #import "AppListResponse.h"
#import "ServerInfoResponse.h" #import "ServerInfoResponse.h"
#import "StreamFrameViewController.h" #import "StreamFrameViewController.h"
#import "LoadingFrameViewController.h" #import "LoadingFrameViewController.h"
#import "ComputerScrollView.h"
#import "TemporaryApp.h"
#import "IdManager.h"
@implementation MainFrameViewController { @implementation MainFrameViewController {
NSOperationQueue* _opQueue; NSOperationQueue* _opQueue;
Host* _selectedHost; TemporaryHost* _selectedHost;
NSString* _uniqueId; NSString* _uniqueId;
NSData* _cert; NSData* _cert;
DiscoveryManager* _discMan; DiscoveryManager* _discMan;
@@ -35,6 +39,7 @@
UIScrollView* hostScrollView; UIScrollView* hostScrollView;
int currentPosition; int currentPosition;
NSArray* _sortedAppList; NSArray* _sortedAppList;
NSCache* _boxArtCache;
} }
static NSMutableSet* hostList; static NSMutableSet* hostList;
@@ -74,7 +79,7 @@ static NSMutableSet* hostList;
// Capture the host here because it can change once we // Capture the host here because it can change once we
// leave the main thread // leave the main thread
Host* host = _selectedHost; TemporaryHost* host = _selectedHost;
if ([host.appList count] > 0) { if ([host.appList count] > 0) {
usingCachedAppList = true; usingCachedAppList = true;
@@ -94,11 +99,26 @@ static NSMutableSet* hostList;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
HttpManager* hMan = [[HttpManager alloc] initWithHost:host.activeAddress uniqueId:_uniqueId deviceName:deviceName cert:_cert]; HttpManager* hMan = [[HttpManager alloc] initWithHost:host.activeAddress uniqueId:_uniqueId deviceName:deviceName cert:_cert];
AppListResponse* appListResp = [[AppListResponse alloc] init];
// Exempt this host from discovery while handling the applist query // Exempt this host from discovery while handling the applist query
[_discMan removeHostFromDiscovery:host]; [_discMan removeHostFromDiscovery:host];
[hMan executeRequestSynchronously:[HttpRequest requestForResponse:appListResp withUrlRequest:[hMan newAppListRequest]]];
// Try up to 5 times to get the app list
AppListResponse* appListResp;
for (int i = 0; i < 5; i++) {
appListResp = [[AppListResponse alloc] init];
[hMan executeRequestSynchronously:[HttpRequest requestForResponse:appListResp withUrlRequest:[hMan newAppListRequest]]];
if (appListResp == nil || ![appListResp isStatusOk] || [appListResp getAppList] == nil) {
Log(LOG_W, @"Failed to get applist on try %d: %@", i, appListResp.statusMessage);
// Wait for one second then retry
[NSThread sleepForTimeInterval:1];
}
else {
Log(LOG_I, @"App list successfully retreived - took %d tries", i);
break;
}
}
[_discMan addHostToDiscovery:host]; [_discMan addHostToDiscovery:host];
if (appListResp == nil || ![appListResp isStatusOk] || [appListResp getAppList] == nil) { if (appListResp == nil || ![appListResp isStatusOk] || [appListResp getAppList] == nil) {
@@ -120,7 +140,7 @@ static NSMutableSet* hostList;
}); });
} else { } else {
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
[self mergeAppLists:[appListResp getAppList] forHost:host]; [self updateApplist:[appListResp getAppList] forHost:host];
if (host != _selectedHost) { if (host != _selectedHost) {
return; return;
@@ -138,12 +158,14 @@ static NSMutableSet* hostList;
}); });
} }
- (void) mergeAppLists:(NSArray*) newList forHost:(Host*)host { - (void) updateApplist:(NSSet*) newList forHost:(TemporaryHost*)host {
DataManager* database = [[DataManager alloc] init]; DataManager* database = [[DataManager alloc] init];
for (App* app in newList) {
BOOL appAlreadyInList = NO; for (TemporaryApp* app in newList) {
for (App* savedApp in host.appList) { BOOL appAlreadyInList = NO;
for (TemporaryApp* savedApp in host.appList) {
if ([app.id isEqualToString:savedApp.id]) { if ([app.id isEqualToString:savedApp.id]) {
savedApp.name = app.name;
savedApp.isRunning = app.isRunning; savedApp.isRunning = app.isRunning;
appAlreadyInList = YES; appAlreadyInList = YES;
break; break;
@@ -151,38 +173,59 @@ static NSMutableSet* hostList;
} }
if (!appAlreadyInList) { if (!appAlreadyInList) {
app.host = host; app.host = host;
[host addAppListObject:app]; [host.appList addObject:app];
} else {
[database removeApp:app];
} }
} }
for (App* app in host.appList) { BOOL appWasRemoved;
BOOL appWasRemoved = YES; do {
for (App* mergedApp in newList) { appWasRemoved = NO;
if ([mergedApp.id isEqualToString:app.id]) {
appWasRemoved = NO; for (TemporaryApp* app in host.appList) {
appWasRemoved = YES;
for (TemporaryApp* mergedApp in newList) {
if ([mergedApp.id isEqualToString:app.id]) {
appWasRemoved = NO;
break;
}
}
if (appWasRemoved) {
// Removing the app mutates the list we're iterating (which isn't legal).
// We need to jump out of this loop and restart enumeration.
[host.appList removeObject:app];
// It's important to remove the app record from the database
// since we'll have a constraint violation now that appList
// doesn't have this app in it.
[database removeApp:app];
break; break;
} }
} }
if (appWasRemoved) {
[host removeAppListObject:app]; // Keep looping until the list is no longer being mutated
[database removeApp:app]; } while (appWasRemoved);
}
} [database updateAppsForExistingHost:host];
[database saveData];
} }
- (void)showHostSelectionView { - (void)showHostSelectionView {
[_appManager stopRetrieving]; [_appManager stopRetrieving];
[[[DataManager alloc] init] saveData];
_selectedHost = nil; _selectedHost = nil;
_computerNameButton.title = @"No Host Selected"; _computerNameButton.title = @"No Host Selected";
[self.collectionView reloadData]; [self.collectionView reloadData];
[self.view addSubview:hostScrollView]; [self.view addSubview:hostScrollView];
} }
- (void) receivedAssetForApp:(App*)app { - (void) receivedAssetForApp:(TemporaryApp*)app {
// Update the box art cache now so we don't have to do it
// on the main thread
[self updateBoxArtCacheForApp:app];
DataManager* dataManager = [[DataManager alloc] init];
[dataManager updateIconForExistingApp: app];
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
[self.collectionView reloadData]; [self.collectionView reloadData];
}); });
@@ -196,7 +239,7 @@ static NSMutableSet* hostList;
[self presentViewController:alert animated:YES completion:nil]; [self presentViewController:alert animated:YES completion:nil];
} }
- (void) hostClicked:(Host *)host view:(UIView *)view { - (void) hostClicked:(TemporaryHost *)host view:(UIView *)view {
// Treat clicks on offline hosts to be long clicks // Treat clicks on offline hosts to be long clicks
// This shows the context menu with wake, delete, etc. rather // This shows the context menu with wake, delete, etc. rather
// than just hanging for a while and failing as we would in this // than just hanging for a while and failing as we would in this
@@ -262,7 +305,7 @@ static NSMutableSet* hostList;
}); });
} }
- (void)hostLongClicked:(Host *)host view:(UIView *)view { - (void)hostLongClicked:(TemporaryHost *)host view:(UIView *)view {
Log(LOG_D, @"Long clicked host: %@", host.name); Log(LOG_D, @"Long clicked host: %@", host.name);
UIAlertController* longClickAlert = [UIAlertController alertControllerWithTitle:host.name message:@"" preferredStyle:UIAlertControllerStyleActionSheet]; UIAlertController* longClickAlert = [UIAlertController alertControllerWithTitle:host.name message:@"" preferredStyle:UIAlertControllerStyleActionSheet];
if (!host.online) { if (!host.online) {
@@ -288,8 +331,8 @@ static NSMutableSet* hostList;
[dataMan removeHost:host]; [dataMan removeHost:host];
@synchronized(hostList) { @synchronized(hostList) {
[hostList removeObject:host]; [hostList removeObject:host];
[self updateAllHosts:[hostList allObjects]];
} }
[self updateAllHosts:[hostList allObjects]];
}]]; }]];
[longClickAlert addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]]; [longClickAlert addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]];
@@ -311,10 +354,8 @@ static NSMutableSet* hostList;
[alertController addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction* action){ [alertController addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction* action){
NSString* hostAddress = ((UITextField*)[[alertController textFields] objectAtIndex:0]).text; NSString* hostAddress = ((UITextField*)[[alertController textFields] objectAtIndex:0]).text;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[_discMan discoverHost:hostAddress withCallback:^(Host* host, NSString* error){ [_discMan discoverHost:hostAddress withCallback:^(TemporaryHost* host, NSString* error){
if (host != nil) { if (host != nil) {
DataManager* dataMan = [[DataManager alloc] init];
[dataMan saveData];
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
@synchronized(hostList) { @synchronized(hostList) {
[hostList addObject:host]; [hostList addObject:host];
@@ -335,14 +376,14 @@ static NSMutableSet* hostList;
[self presentViewController:alertController animated:YES completion:nil]; [self presentViewController:alertController animated:YES completion:nil];
} }
- (void) appClicked:(App *)app { - (void) appClicked:(TemporaryApp *)app {
Log(LOG_D, @"Clicked app: %@", app.name); Log(LOG_D, @"Clicked app: %@", app.name);
_streamConfig = [[StreamConfiguration alloc] init]; _streamConfig = [[StreamConfiguration alloc] init];
_streamConfig.host = app.host.activeAddress; _streamConfig.host = app.host.activeAddress;
_streamConfig.appID = app.id; _streamConfig.appID = app.id;
DataManager* dataMan = [[DataManager alloc] init]; DataManager* dataMan = [[DataManager alloc] init];
Settings* streamSettings = [dataMan retrieveSettings]; TemporarySettings* streamSettings = [dataMan getSettings];
_streamConfig.frameRate = [streamSettings.framerate intValue]; _streamConfig.frameRate = [streamSettings.framerate intValue];
_streamConfig.bitRate = [streamSettings.bitrate intValue]; _streamConfig.bitRate = [streamSettings.bitrate intValue];
@@ -355,7 +396,7 @@ static NSMutableSet* hostList;
[[self revealViewController] revealToggle:self]; [[self revealViewController] revealToggle:self];
} }
App* currentApp = [self findRunningApp:app.host]; TemporaryApp* currentApp = [self findRunningApp:app.host];
if (currentApp != nil) { if (currentApp != nil) {
UIAlertController* alertController = [UIAlertController UIAlertController* alertController = [UIAlertController
alertControllerWithTitle: app.name alertControllerWithTitle: app.name
@@ -421,8 +462,8 @@ static NSMutableSet* hostList;
} }
} }
- (App*) findRunningApp:(Host*)host { - (TemporaryApp*) findRunningApp:(TemporaryHost*)host {
for (App* app in host.appList) { for (TemporaryApp* app in host.appList) {
if (app.isRunning) { if (app.isRunning) {
return app; return app;
} }
@@ -477,7 +518,7 @@ static NSMutableSet* hostList;
// Set up crypto // Set up crypto
[CryptoManager generateKeyPairUsingSSl]; [CryptoManager generateKeyPairUsingSSl];
_uniqueId = [CryptoManager getUniqueID]; _uniqueId = [IdManager getUniqueId];
_cert = [CryptoManager readCertFromFile]; _cert = [CryptoManager readCertFromFile];
_appManager = [[AppAssetManager alloc] initWithCallback:self]; _appManager = [[AppAssetManager alloc] initWithCallback:self];
@@ -488,19 +529,54 @@ static NSMutableSet* hostList;
hostList = [[NSMutableSet alloc] init]; hostList = [[NSMutableSet alloc] init];
} }
_boxArtCache = [[NSCache alloc] init];
[self setAutomaticallyAdjustsScrollViewInsets:NO]; [self setAutomaticallyAdjustsScrollViewInsets:NO];
hostScrollView = [[UIScrollView alloc] init]; hostScrollView = [[ComputerScrollView alloc] init];
hostScrollView.frame = CGRectMake(0, self.navigationController.navigationBar.frame.origin.y + self.navigationController.navigationBar.frame.size.height, self.view.frame.size.width, self.view.frame.size.height / 2); hostScrollView.frame = CGRectMake(0, self.navigationController.navigationBar.frame.origin.y + self.navigationController.navigationBar.frame.size.height, self.view.frame.size.width, self.view.frame.size.height / 2);
[hostScrollView setShowsHorizontalScrollIndicator:NO]; [hostScrollView setShowsHorizontalScrollIndicator:NO];
hostScrollView.delaysContentTouches = NO;
UIButton* pullArrow = [[UIButton alloc] init];
[pullArrow setImage:[UIImage imageNamed:@"PullArrow"] forState:UIControlStateNormal];
[pullArrow sizeToFit];
pullArrow.frame = CGRectMake(0,
self.collectionView.frame.size.height / 2 - pullArrow.frame.size.height / 2 - self.navigationController.navigationBar.frame.size.height,
pullArrow.frame.size.width,
pullArrow.frame.size.height);
[self.view addSubview:pullArrow];
self.collectionView.delaysContentTouches = NO;
self.collectionView.allowsMultipleSelection = NO;
self.collectionView.multipleTouchEnabled = NO;
[self retrieveSavedHosts]; [self retrieveSavedHosts];
_discMan = [[DiscoveryManager alloc] initWithHosts:[hostList allObjects] andCallback:self]; _discMan = [[DiscoveryManager alloc] initWithHosts:[hostList allObjects] andCallback:self];
[[NSNotificationCenter defaultCenter] addObserver: self
selector: @selector(handleReturnToForeground)
name: UIApplicationWillEnterForegroundNotification
object: nil];
[self updateHosts]; [self updateHosts];
[self.view addSubview:hostScrollView]; [self.view addSubview:hostScrollView];
} }
-(void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
-(void)handleReturnToForeground
{
// This will refresh the applist
if (_selectedHost != nil) {
[self hostClicked:_selectedHost view:nil];
}
}
- (void)viewDidAppear:(BOOL)animated - (void)viewDidAppear:(BOOL)animated
{ {
[super viewDidAppear:animated]; [super viewDidAppear:animated];
@@ -513,10 +589,7 @@ static NSMutableSet* hostList;
[_discMan startDiscovery]; [_discMan startDiscovery];
// This will refresh the applist [self handleReturnToForeground];
if (_selectedHost != nil) {
[self hostClicked:_selectedHost view:nil];
}
} }
- (void)viewDidDisappear:(BOOL)animated - (void)viewDidDisappear:(BOOL)animated
@@ -525,18 +598,18 @@ static NSMutableSet* hostList;
// when discovery stops, we must create a new instance because you cannot restart an NSOperation when it is finished // when discovery stops, we must create a new instance because you cannot restart an NSOperation when it is finished
[_discMan stopDiscovery]; [_discMan stopDiscovery];
// In case the host objects were updated in the background // Purge the box art cache
[[[DataManager alloc] init] saveData]; [_boxArtCache removeAllObjects];
} }
- (void) retrieveSavedHosts { - (void) retrieveSavedHosts {
DataManager* dataMan = [[DataManager alloc] init]; DataManager* dataMan = [[DataManager alloc] init];
NSArray* hosts = [dataMan retrieveHosts]; NSArray* hosts = [dataMan getHosts];
@synchronized(hostList) { @synchronized(hostList) {
[hostList addObjectsFromArray:hosts]; [hostList addObjectsFromArray:hosts];
// Initialize the non-persistent host state // Initialize the non-persistent host state
for (Host* host in hostList) { for (TemporaryHost* host in hostList) {
if (host.activeAddress == nil) { if (host.activeAddress == nil) {
host.activeAddress = host.localAddress; host.activeAddress = host.localAddress;
} }
@@ -553,7 +626,7 @@ static NSMutableSet* hostList;
- (void) updateAllHosts:(NSArray *)hosts { - (void) updateAllHosts:(NSArray *)hosts {
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
Log(LOG_D, @"New host list:"); Log(LOG_D, @"New host list:");
for (Host* host in hosts) { for (TemporaryHost* host in hosts) {
Log(LOG_D, @"Host: \n{\n\t name:%@ \n\t address:%@ \n\t localAddress:%@ \n\t externalAddress:%@ \n\t uuid:%@ \n\t mac:%@ \n\t pairState:%d \n\t online:%d \n\t activeAddress:%@ \n}", host.name, host.address, host.localAddress, host.externalAddress, host.uuid, host.mac, host.pairState, host.online, host.activeAddress); Log(LOG_D, @"Host: \n{\n\t name:%@ \n\t address:%@ \n\t localAddress:%@ \n\t externalAddress:%@ \n\t uuid:%@ \n\t mac:%@ \n\t pairState:%d \n\t online:%d \n\t activeAddress:%@ \n}", host.name, host.address, host.localAddress, host.externalAddress, host.uuid, host.mac, host.pairState, host.online, host.activeAddress);
} }
@synchronized(hostList) { @synchronized(hostList) {
@@ -571,13 +644,16 @@ static NSMutableSet* hostList;
UIComputerView* compView; UIComputerView* compView;
float prevEdge = -1; float prevEdge = -1;
@synchronized (hostList) { @synchronized (hostList) {
for (Host* comp in hostList) { // Sort the host list in alphabetical order
NSArray* sortedHostList = [[hostList allObjects] sortedArrayUsingSelector:@selector(compareName:)];
for (TemporaryHost* comp in sortedHostList) {
compView = [[UIComputerView alloc] initWithComputer:comp andCallback:self]; compView = [[UIComputerView alloc] initWithComputer:comp andCallback:self];
compView.center = CGPointMake([self getCompViewX:compView addComp:addComp prevEdge:prevEdge], hostScrollView.frame.size.height / 2); compView.center = CGPointMake([self getCompViewX:compView addComp:addComp prevEdge:prevEdge], hostScrollView.frame.size.height / 2);
prevEdge = compView.frame.origin.x + compView.frame.size.width; prevEdge = compView.frame.origin.x + compView.frame.size.width;
[hostScrollView addSubview:compView]; [hostScrollView addSubview:compView];
} }
} }
prevEdge = [self getCompViewX:addComp addComp:addComp prevEdge:prevEdge]; prevEdge = [self getCompViewX:addComp addComp:addComp prevEdge:prevEdge];
addComp.center = CGPointMake(prevEdge, hostScrollView.frame.size.height / 2); addComp.center = CGPointMake(prevEdge, hostScrollView.frame.size.height / 2);
@@ -593,7 +669,31 @@ static NSMutableSet* hostList;
} }
} }
- (void) updateAppsForHost:(Host*)host { // This function forces immediate decoding of the UIImage, rather
// than the default lazy decoding that results in janky scrolling.
+ (UIImage*) loadBoxArtForCaching:(TemporaryApp*)app {
CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)app.image, NULL);
CGImageRef cgImage = CGImageSourceCreateImageAtIndex(source, 0, (__bridge CFDictionaryRef)@{(id)kCGImageSourceShouldCacheImmediately: (id)kCFBooleanTrue});
UIImage *boxArt = [UIImage imageWithCGImage:cgImage];
CGImageRelease(cgImage);
CFRelease(source);
return boxArt;
}
- (void) updateBoxArtCacheForApp:(TemporaryApp*)app {
if (app.image == nil) {
[_boxArtCache removeObjectForKey:app];
}
else if ([_boxArtCache objectForKey:app] == nil) {
[_boxArtCache setObject:[MainFrameViewController loadBoxArtForCaching:app] forKey:app];
}
}
- (void) updateAppsForHost:(TemporaryHost*)host {
if (host != _selectedHost) { if (host != _selectedHost) {
Log(LOG_W, @"Mismatched host during app update"); Log(LOG_W, @"Mismatched host during app update");
return; return;
@@ -602,6 +702,33 @@ static NSMutableSet* hostList;
_sortedAppList = [host.appList allObjects]; _sortedAppList = [host.appList allObjects];
_sortedAppList = [_sortedAppList sortedArrayUsingSelector:@selector(compareName:)]; _sortedAppList = [_sortedAppList sortedArrayUsingSelector:@selector(compareName:)];
// Split the sorted array in half to allow 2 jobs to process app assets at once
NSArray *firstHalf;
NSArray *secondHalf;
NSRange range;
range.location = 0;
range.length = [_sortedAppList count] / 2;
firstHalf = [_sortedAppList subarrayWithRange:range];
range.location = range.length;
range.length = [_sortedAppList count] - range.length;
secondHalf = [_sortedAppList subarrayWithRange:range];
// Start 2 jobs
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (TemporaryApp* app in firstHalf) {
[self updateBoxArtCacheForApp:app];
}
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (TemporaryApp* app in secondHalf) {
[self updateBoxArtCacheForApp:app];
}
});
[hostScrollView removeFromSuperview]; [hostScrollView removeFromSuperview];
[self.collectionView reloadData]; [self.collectionView reloadData];
} }
@@ -609,8 +736,8 @@ static NSMutableSet* hostList;
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell* cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"AppCell" forIndexPath:indexPath]; UICollectionViewCell* cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"AppCell" forIndexPath:indexPath];
App* app = _sortedAppList[indexPath.row]; TemporaryApp* app = _sortedAppList[indexPath.row];
UIAppView* appView = [[UIAppView alloc] initWithApp:app andCallback:self]; UIAppView* appView = [[UIAppView alloc] initWithApp:app cache:_boxArtCache andCallback:self];
[appView updateAppImage]; [appView updateAppImage];
if (appView.bounds.size.width > 10.0) { if (appView.bounds.size.width > 10.0) {
@@ -621,7 +748,19 @@ static NSMutableSet* hostList;
[cell.subviews.firstObject removeFromSuperview]; // Remove a view that was previously added [cell.subviews.firstObject removeFromSuperview]; // Remove a view that was previously added
[cell addSubview:appView]; [cell addSubview:appView];
UIBezierPath *shadowPath = [UIBezierPath bezierPathWithRect:cell.bounds];
cell.layer.masksToBounds = NO;
cell.layer.shadowColor = [UIColor blackColor].CGColor;
cell.layer.shadowOffset = CGSizeMake(1.0f, 5.0f);
cell.layer.shadowOpacity = 0.5f;
cell.layer.shadowPath = shadowPath.CGPath;
cell.layer.borderColor = [[UIColor colorWithRed:0 green:0 blue:0 alpha:0.3f] CGColor];
cell.layer.borderWidth = 1;
cell.exclusiveTouch = YES;
return cell; return cell;
} }
@@ -7,7 +7,7 @@
// //
#import "SettingsViewController.h" #import "SettingsViewController.h"
#import "Settings.h" #import "TemporarySettings.h"
#import "DataManager.h" #import "DataManager.h"
#define BITRATE_INTERVAL 500 // in kbps #define BITRATE_INTERVAL 500 // in kbps
@@ -22,7 +22,7 @@ static NSString* bitrateFormat = @"Bitrate: %.1f Mbps";
[super viewDidLoad]; [super viewDidLoad];
DataManager* dataMan = [[DataManager alloc] init]; DataManager* dataMan = [[DataManager alloc] init];
Settings* currentSettings = [dataMan retrieveSettings]; TemporarySettings* currentSettings = [dataMan getSettings];
// Bitrate is persisted in kbps // Bitrate is persisted in kbps
_bitrate = [currentSettings.bitrate integerValue]; _bitrate = [currentSettings.bitrate integerValue];
@@ -33,18 +33,44 @@ static NSString* bitrateFormat = @"Bitrate: %.1f Mbps";
} else if ([currentSettings.height integerValue] == 1080) { } else if ([currentSettings.height integerValue] == 1080) {
resolution = 1; resolution = 1;
} else { } else {
resolution = 2; resolution = 0;
} }
NSInteger onscreenControls = [currentSettings.onscreenControls integerValue]; NSInteger onscreenControls = [currentSettings.onscreenControls integerValue];
[self.resolutionSelector setSelectedSegmentIndex:resolution]; [self.resolutionSelector setSelectedSegmentIndex:resolution];
[self.resolutionSelector addTarget:self action:@selector(newResolutionFpsChosen) forControlEvents:UIControlEventValueChanged];
[self.framerateSelector setSelectedSegmentIndex:framerate]; [self.framerateSelector setSelectedSegmentIndex:framerate];
[self.framerateSelector addTarget:self action:@selector(newResolutionFpsChosen) forControlEvents:UIControlEventValueChanged];
[self.onscreenControlSelector setSelectedSegmentIndex:onscreenControls]; [self.onscreenControlSelector setSelectedSegmentIndex:onscreenControls];
[self.bitrateSlider setValue:(_bitrate / BITRATE_INTERVAL) animated:YES]; [self.bitrateSlider setValue:(_bitrate / BITRATE_INTERVAL) animated:YES];
[self.bitrateSlider addTarget:self action:@selector(bitrateSliderMoved) forControlEvents:UIControlEventValueChanged]; [self.bitrateSlider addTarget:self action:@selector(bitrateSliderMoved) forControlEvents:UIControlEventValueChanged];
[self updateBitrateText]; [self updateBitrateText];
} }
- (void) newResolutionFpsChosen {
NSInteger frameRate = [self getChosenFrameRate];
NSInteger resHeight = [self getChosenStreamHeight];
NSInteger defaultBitrate;
// 1080p60 is 20 Mbps
if (frameRate == 60 && resHeight == 1080) {
defaultBitrate = 20000;
}
// 720p60 and 1080p30 are 10 Mbps
else if (frameRate == 60 || resHeight == 1080) {
defaultBitrate = 10000;
}
// 720p30 is 5 Mbps
else {
defaultBitrate = 5000;
}
_bitrate = defaultBitrate;
[self.bitrateSlider setValue:defaultBitrate / BITRATE_INTERVAL animated:YES];
[self updateBitrateText];
}
- (void) bitrateSliderMoved { - (void) bitrateSliderMoved {
_bitrate = BITRATE_INTERVAL * (int)self.bitrateSlider.value; _bitrate = BITRATE_INTERVAL * (int)self.bitrateSlider.value;
[self updateBitrateText]; [self updateBitrateText];
@@ -55,25 +81,23 @@ static NSString* bitrateFormat = @"Bitrate: %.1f Mbps";
[self.bitrateLabel setText:[NSString stringWithFormat:bitrateFormat, _bitrate / 1000.]]; [self.bitrateLabel setText:[NSString stringWithFormat:bitrateFormat, _bitrate / 1000.]];
} }
- (NSInteger) getChosenFrameRate {
return [self.framerateSelector selectedSegmentIndex] == 0 ? 30 : 60;
}
- (NSInteger) getChosenStreamHeight {
return [self.resolutionSelector selectedSegmentIndex] == 0 ? 720 : 1080;
}
- (NSInteger) getChosenStreamWidth {
return [self getChosenStreamHeight] == 720 ? 1280 : 1920;
}
- (void) saveSettings { - (void) saveSettings {
DataManager* dataMan = [[DataManager alloc] init]; DataManager* dataMan = [[DataManager alloc] init];
NSInteger framerate = [self.framerateSelector selectedSegmentIndex] == 0 ? 30 : 60; NSInteger framerate = [self getChosenFrameRate];
NSInteger height; NSInteger height = [self getChosenStreamHeight];
NSInteger width; NSInteger width = [self getChosenStreamWidth];
if ([self.resolutionSelector selectedSegmentIndex] == 2) {
// Get screen native resolution
height = [UIScreen mainScreen].bounds.size.height * [UIScreen mainScreen].scale;
width = [UIScreen mainScreen].bounds.size.width * [UIScreen mainScreen].scale;
UIAlertController *alertResolution = [UIAlertController alertControllerWithTitle:@"Native resolution" message:[NSString stringWithFormat:@"You must select the following resolution in the game settings: %lix%li", (long)width, (long)height] preferredStyle:UIAlertControllerStyleAlert];
[alertResolution addAction:[UIAlertAction actionWithTitle:@"Ok" style:UIAlertActionStyleCancel handler:nil]];
[self presentViewController:alertResolution animated:YES completion:nil];
}
else
{
height = [self.resolutionSelector selectedSegmentIndex] == 0 ? 720 : 1080;
width = height == 720 ? 1280 : 1920;
}
NSInteger onscreenControls = [self.onscreenControlSelector selectedSegmentIndex]; NSInteger onscreenControls = [self.onscreenControlSelector selectedSegmentIndex];
[dataMan saveSettingsWithBitrate:_bitrate framerate:framerate height:height width:width onscreenControls:onscreenControls]; [dataMan saveSettingsWithBitrate:_bitrate framerate:framerate height:height width:width onscreenControls:onscreenControls];
} }
@@ -114,6 +114,8 @@
}]]; }]];
[self presentViewController:alert animated:YES completion:nil]; [self presentViewController:alert animated:YES completion:nil];
}); });
[_streamMan stopStreamInternal];
} }
- (void) launchFailed:(NSString*)message { - (void) launchFailed:(NSString*)message {
+43 -5
View File
@@ -3,11 +3,14 @@
archiveVersion = 1; archiveVersion = 1;
classes = { classes = {
}; };
objectVersion = 46; objectVersion = 47;
objects = { objects = {
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
9832D1361BBCD5C50036EF48 /* TemporaryApp.m in Sources */ = {isa = PBXBuildFile; fileRef = 9832D1351BBCD5C50036EF48 /* TemporaryApp.m */; };
987140041B04542F00AB57D5 /* libmoonlight-common.a in Frameworks */ = {isa = PBXBuildFile; fileRef = FB1E43101AE8B0F200AFF679 /* libmoonlight-common.a */; }; 987140041B04542F00AB57D5 /* libmoonlight-common.a in Frameworks */ = {isa = PBXBuildFile; fileRef = FB1E43101AE8B0F200AFF679 /* libmoonlight-common.a */; };
98D5856D1C0EA79600F6CC00 /* TemporaryHost.m in Sources */ = {isa = PBXBuildFile; fileRef = 98D5856C1C0EA79600F6CC00 /* TemporaryHost.m */; };
98D585701C0ED0E800F6CC00 /* TemporarySettings.m in Sources */ = {isa = PBXBuildFile; fileRef = 98D5856F1C0ED0E800F6CC00 /* TemporarySettings.m */; };
9E5D600B1A5A5A3900689918 /* Apache License.txt in Resources */ = {isa = PBXBuildFile; fileRef = 9E5D5FF81A5A5A3900689918 /* Apache License.txt */; }; 9E5D600B1A5A5A3900689918 /* Apache License.txt in Resources */ = {isa = PBXBuildFile; fileRef = 9E5D5FF81A5A5A3900689918 /* Apache License.txt */; };
9E5D600C1A5A5A3900689918 /* Roboto-Black.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 9E5D5FF91A5A5A3900689918 /* Roboto-Black.ttf */; }; 9E5D600C1A5A5A3900689918 /* Roboto-Black.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 9E5D5FF91A5A5A3900689918 /* Roboto-Black.ttf */; };
9E5D600E1A5A5A3900689918 /* Roboto-Bold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 9E5D5FFB1A5A5A3900689918 /* Roboto-Bold.ttf */; }; 9E5D600E1A5A5A3900689918 /* Roboto-Bold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 9E5D5FFB1A5A5A3900689918 /* Roboto-Bold.ttf */; };
@@ -15,6 +18,8 @@
9E5D60131A5A5A3900689918 /* Roboto-Medium.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 9E5D60001A5A5A3900689918 /* Roboto-Medium.ttf */; }; 9E5D60131A5A5A3900689918 /* Roboto-Medium.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 9E5D60001A5A5A3900689918 /* Roboto-Medium.ttf */; };
9E5D60151A5A5A3900689918 /* Roboto-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 9E5D60021A5A5A3900689918 /* Roboto-Regular.ttf */; }; 9E5D60151A5A5A3900689918 /* Roboto-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 9E5D60021A5A5A3900689918 /* Roboto-Regular.ttf */; };
9E5D60161A5A5A3900689918 /* Roboto-Thin.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 9E5D60031A5A5A3900689918 /* Roboto-Thin.ttf */; }; 9E5D60161A5A5A3900689918 /* Roboto-Thin.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 9E5D60031A5A5A3900689918 /* Roboto-Thin.ttf */; };
FB1D59971BBCCB6400F482CA /* ComputerScrollView.m in Sources */ = {isa = PBXBuildFile; fileRef = FB1D59961BBCCB6400F482CA /* ComputerScrollView.m */; };
FB1D599A1BBCCD7E00F482CA /* AppCollectionView.m in Sources */ = {isa = PBXBuildFile; fileRef = FB1D59991BBCCD7E00F482CA /* AppCollectionView.m */; };
FB290CF219B2C406004C83CF /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FB290CF119B2C406004C83CF /* Foundation.framework */; }; FB290CF219B2C406004C83CF /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FB290CF119B2C406004C83CF /* Foundation.framework */; };
FB290CF419B2C406004C83CF /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FB290CF319B2C406004C83CF /* CoreGraphics.framework */; }; FB290CF419B2C406004C83CF /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FB290CF319B2C406004C83CF /* CoreGraphics.framework */; };
FB290CF619B2C406004C83CF /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FB290CF519B2C406004C83CF /* UIKit.framework */; }; FB290CF619B2C406004C83CF /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FB290CF519B2C406004C83CF /* UIKit.framework */; };
@@ -34,6 +39,7 @@
FB4678FF1A565DAC00377732 /* WakeOnLanManager.m in Sources */ = {isa = PBXBuildFile; fileRef = FB4678FE1A565DAC00377732 /* WakeOnLanManager.m */; }; FB4678FF1A565DAC00377732 /* WakeOnLanManager.m in Sources */ = {isa = PBXBuildFile; fileRef = FB4678FE1A565DAC00377732 /* WakeOnLanManager.m */; };
FB4679011A57048000377732 /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FB4679001A57048000377732 /* CoreFoundation.framework */; }; FB4679011A57048000377732 /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FB4679001A57048000377732 /* CoreFoundation.framework */; };
FB4A23B81A9D3637004D2EF2 /* LoadingFrameViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = FB4A23B71A9D3637004D2EF2 /* LoadingFrameViewController.m */; }; FB4A23B81A9D3637004D2EF2 /* LoadingFrameViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = FB4A23B71A9D3637004D2EF2 /* LoadingFrameViewController.m */; };
FB53E1431BE5DC4400CD6ECE /* IdManager.m in Sources */ = {isa = PBXBuildFile; fileRef = FB53E1421BE5DC4400CD6ECE /* IdManager.m */; };
FB6549561A57907E001C8F39 /* DiscoveryWorker.m in Sources */ = {isa = PBXBuildFile; fileRef = FB6549551A57907E001C8F39 /* DiscoveryWorker.m */; }; FB6549561A57907E001C8F39 /* DiscoveryWorker.m in Sources */ = {isa = PBXBuildFile; fileRef = FB6549551A57907E001C8F39 /* DiscoveryWorker.m */; };
FB7E794419C8B71B00A15F68 /* libiconv.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = FB7E794319C8B71B00A15F68 /* libiconv.dylib */; }; FB7E794419C8B71B00A15F68 /* libiconv.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = FB7E794319C8B71B00A15F68 /* libiconv.dylib */; };
FB89462819F646E200339C8A /* CryptoManager.m in Sources */ = {isa = PBXBuildFile; fileRef = FB89460619F646E200339C8A /* CryptoManager.m */; }; FB89462819F646E200339C8A /* CryptoManager.m in Sources */ = {isa = PBXBuildFile; fileRef = FB89460619F646E200339C8A /* CryptoManager.m */; };
@@ -91,7 +97,13 @@
/* End PBXContainerItemProxy section */ /* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
9832D1341BBCD5C50036EF48 /* TemporaryApp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = TemporaryApp.h; path = Database/TemporaryApp.h; sourceTree = "<group>"; };
9832D1351BBCD5C50036EF48 /* TemporaryApp.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = TemporaryApp.m; path = Database/TemporaryApp.m; sourceTree = "<group>"; };
98A03B4519F3514B00861ACA /* moonlight-common.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = "moonlight-common.xcodeproj"; path = "limelight-common-c/moonlight-common.xcodeproj"; sourceTree = "<group>"; }; 98A03B4519F3514B00861ACA /* moonlight-common.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = "moonlight-common.xcodeproj"; path = "limelight-common-c/moonlight-common.xcodeproj"; sourceTree = "<group>"; };
98D5856B1C0EA79600F6CC00 /* TemporaryHost.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = TemporaryHost.h; path = Database/TemporaryHost.h; sourceTree = "<group>"; };
98D5856C1C0EA79600F6CC00 /* TemporaryHost.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = TemporaryHost.m; path = Database/TemporaryHost.m; sourceTree = "<group>"; };
98D5856E1C0ED0E800F6CC00 /* TemporarySettings.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = TemporarySettings.h; path = Database/TemporarySettings.h; sourceTree = "<group>"; };
98D5856F1C0ED0E800F6CC00 /* TemporarySettings.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = TemporarySettings.m; path = Database/TemporarySettings.m; sourceTree = "<group>"; };
9E5D5FF81A5A5A3900689918 /* Apache License.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "Apache License.txt"; sourceTree = "<group>"; }; 9E5D5FF81A5A5A3900689918 /* Apache License.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "Apache License.txt"; sourceTree = "<group>"; };
9E5D5FF91A5A5A3900689918 /* Roboto-Black.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Roboto-Black.ttf"; sourceTree = "<group>"; }; 9E5D5FF91A5A5A3900689918 /* Roboto-Black.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Roboto-Black.ttf"; sourceTree = "<group>"; };
9E5D5FFB1A5A5A3900689918 /* Roboto-Bold.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Roboto-Bold.ttf"; sourceTree = "<group>"; }; 9E5D5FFB1A5A5A3900689918 /* Roboto-Bold.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Roboto-Bold.ttf"; sourceTree = "<group>"; };
@@ -99,6 +111,10 @@
9E5D60001A5A5A3900689918 /* Roboto-Medium.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Roboto-Medium.ttf"; sourceTree = "<group>"; }; 9E5D60001A5A5A3900689918 /* Roboto-Medium.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Roboto-Medium.ttf"; sourceTree = "<group>"; };
9E5D60021A5A5A3900689918 /* Roboto-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Roboto-Regular.ttf"; sourceTree = "<group>"; }; 9E5D60021A5A5A3900689918 /* Roboto-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Roboto-Regular.ttf"; sourceTree = "<group>"; };
9E5D60031A5A5A3900689918 /* Roboto-Thin.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Roboto-Thin.ttf"; sourceTree = "<group>"; }; 9E5D60031A5A5A3900689918 /* Roboto-Thin.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Roboto-Thin.ttf"; sourceTree = "<group>"; };
FB1D59951BBCCB6400F482CA /* ComputerScrollView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ComputerScrollView.h; sourceTree = "<group>"; };
FB1D59961BBCCB6400F482CA /* ComputerScrollView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ComputerScrollView.m; sourceTree = "<group>"; };
FB1D59981BBCCD7E00F482CA /* AppCollectionView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppCollectionView.h; sourceTree = "<group>"; };
FB1D59991BBCCD7E00F482CA /* AppCollectionView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppCollectionView.m; sourceTree = "<group>"; };
FB290CEE19B2C406004C83CF /* Moonlight.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Moonlight.app; sourceTree = BUILT_PRODUCTS_DIR; }; FB290CEE19B2C406004C83CF /* Moonlight.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Moonlight.app; sourceTree = BUILT_PRODUCTS_DIR; };
FB290CF119B2C406004C83CF /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; FB290CF119B2C406004C83CF /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
FB290CF319B2C406004C83CF /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; FB290CF319B2C406004C83CF /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; };
@@ -127,6 +143,9 @@
FB4679001A57048000377732 /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = System/Library/Frameworks/CoreFoundation.framework; sourceTree = SDKROOT; }; FB4679001A57048000377732 /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = System/Library/Frameworks/CoreFoundation.framework; sourceTree = SDKROOT; };
FB4A23B61A9D3637004D2EF2 /* LoadingFrameViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LoadingFrameViewController.h; sourceTree = "<group>"; }; FB4A23B61A9D3637004D2EF2 /* LoadingFrameViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LoadingFrameViewController.h; sourceTree = "<group>"; };
FB4A23B71A9D3637004D2EF2 /* LoadingFrameViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LoadingFrameViewController.m; sourceTree = "<group>"; }; FB4A23B71A9D3637004D2EF2 /* LoadingFrameViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LoadingFrameViewController.m; sourceTree = "<group>"; };
FB53E1411BE5DC4400CD6ECE /* IdManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IdManager.h; sourceTree = "<group>"; };
FB53E1421BE5DC4400CD6ECE /* IdManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IdManager.m; sourceTree = "<group>"; };
FB53E1441BE5DCBC00CD6ECE /* Moonlight v1.0-2.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Moonlight v1.0-2.xcdatamodel"; sourceTree = "<group>"; };
FB6549541A57907E001C8F39 /* DiscoveryWorker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DiscoveryWorker.h; sourceTree = "<group>"; }; FB6549541A57907E001C8F39 /* DiscoveryWorker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DiscoveryWorker.h; sourceTree = "<group>"; };
FB6549551A57907E001C8F39 /* DiscoveryWorker.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DiscoveryWorker.m; sourceTree = "<group>"; }; FB6549551A57907E001C8F39 /* DiscoveryWorker.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DiscoveryWorker.m; sourceTree = "<group>"; };
FB6549621A60B4A9001C8F39 /* Limelight 0.3.1.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Limelight 0.3.1.xcdatamodel"; sourceTree = "<group>"; }; FB6549621A60B4A9001C8F39 /* Limelight 0.3.1.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Limelight 0.3.1.xcdatamodel"; sourceTree = "<group>"; };
@@ -397,6 +416,10 @@
FB89463719F6473800339C8A /* Launch Screen.xib */, FB89463719F6473800339C8A /* Launch Screen.xib */,
FB290D0819B2C406004C83CF /* Images.xcassets */, FB290D0819B2C406004C83CF /* Images.xcassets */,
FB290D0519B2C406004C83CF /* Limelight.xcdatamodeld */, FB290D0519B2C406004C83CF /* Limelight.xcdatamodeld */,
FB1D59951BBCCB6400F482CA /* ComputerScrollView.h */,
FB1D59961BBCCB6400F482CA /* ComputerScrollView.m */,
FB1D59981BBCCD7E00F482CA /* AppCollectionView.h */,
FB1D59991BBCCD7E00F482CA /* AppCollectionView.m */,
); );
path = Limelight; path = Limelight;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -419,6 +442,8 @@
FB89460619F646E200339C8A /* CryptoManager.m */, FB89460619F646E200339C8A /* CryptoManager.m */,
FB89460719F646E200339C8A /* mkcert.c */, FB89460719F646E200339C8A /* mkcert.c */,
FB89460819F646E200339C8A /* mkcert.h */, FB89460819F646E200339C8A /* mkcert.h */,
FB53E1411BE5DC4400CD6ECE /* IdManager.h */,
FB53E1421BE5DC4400CD6ECE /* IdManager.m */,
); );
path = Crypto; path = Crypto;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -712,6 +737,12 @@
FBD3495D1A004412002D2A60 /* Settings.m */, FBD3495D1A004412002D2A60 /* Settings.m */,
FBD349601A0089F6002D2A60 /* DataManager.h */, FBD349601A0089F6002D2A60 /* DataManager.h */,
FBD349611A0089F6002D2A60 /* DataManager.m */, FBD349611A0089F6002D2A60 /* DataManager.m */,
9832D1341BBCD5C50036EF48 /* TemporaryApp.h */,
9832D1351BBCD5C50036EF48 /* TemporaryApp.m */,
98D5856B1C0EA79600F6CC00 /* TemporaryHost.h */,
98D5856C1C0EA79600F6CC00 /* TemporaryHost.m */,
98D5856E1C0ED0E800F6CC00 /* TemporarySettings.h */,
98D5856F1C0ED0E800F6CC00 /* TemporarySettings.m */,
); );
name = Database; name = Database;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -744,7 +775,7 @@
isa = PBXProject; isa = PBXProject;
attributes = { attributes = {
LastUpgradeCheck = 0700; LastUpgradeCheck = 0700;
ORGANIZATIONNAME = "Limelight Stream"; ORGANIZATIONNAME = "Moonlight Stream";
TargetAttributes = { TargetAttributes = {
FB290CED19B2C406004C83CF = { FB290CED19B2C406004C83CF = {
DevelopmentTeam = DM46QST4M7; DevelopmentTeam = DM46QST4M7;
@@ -752,7 +783,7 @@
}; };
}; };
buildConfigurationList = FB290CE919B2C406004C83CF /* Build configuration list for PBXProject "Moonlight" */; buildConfigurationList = FB290CE919B2C406004C83CF /* Build configuration list for PBXProject "Moonlight" */;
compatibilityVersion = "Xcode 3.2"; compatibilityVersion = "Xcode 6.3";
developmentRegion = English; developmentRegion = English;
hasScannedForEncodings = 0; hasScannedForEncodings = 0;
knownRegions = ( knownRegions = (
@@ -821,20 +852,26 @@
FB89462F19F646E200339C8A /* Connection.m in Sources */, FB89462F19F646E200339C8A /* Connection.m in Sources */,
FB9AFD321A7D867C00872C98 /* AppAssetRetriever.m in Sources */, FB9AFD321A7D867C00872C98 /* AppAssetRetriever.m in Sources */,
FB4678FF1A565DAC00377732 /* WakeOnLanManager.m in Sources */, FB4678FF1A565DAC00377732 /* WakeOnLanManager.m in Sources */,
FB53E1431BE5DC4400CD6ECE /* IdManager.m in Sources */,
FB89462919F646E200339C8A /* mkcert.c in Sources */, FB89462919F646E200339C8A /* mkcert.c in Sources */,
FB9AFD281A7C84ED00872C98 /* HttpResponse.m in Sources */, FB9AFD281A7C84ED00872C98 /* HttpResponse.m in Sources */,
FBDE86E019F7A837001C18A8 /* UIComputerView.m in Sources */, FBDE86E019F7A837001C18A8 /* UIComputerView.m in Sources */,
FB89463019F646E200339C8A /* StreamConfiguration.m in Sources */, FB89463019F646E200339C8A /* StreamConfiguration.m in Sources */,
FBFCB3311B50B29400089F8A /* Host.m in Sources */, FBFCB3311B50B29400089F8A /* Host.m in Sources */,
FBD3495319FF36FB002D2A60 /* SWRevealViewController.m in Sources */, FBD3495319FF36FB002D2A60 /* SWRevealViewController.m in Sources */,
FB1D59971BBCCB6400F482CA /* ComputerScrollView.m in Sources */,
FBD3495019FF2174002D2A60 /* SettingsViewController.m in Sources */, FBD3495019FF2174002D2A60 /* SettingsViewController.m in Sources */,
FB89462C19F646E200339C8A /* HttpManager.m in Sources */, FB89462C19F646E200339C8A /* HttpManager.m in Sources */,
FB89462D19F646E200339C8A /* MDNSManager.m in Sources */, FB89462D19F646E200339C8A /* MDNSManager.m in Sources */,
FB89462B19F646E200339C8A /* StreamView.m in Sources */, FB89462B19F646E200339C8A /* StreamView.m in Sources */,
FB4678FA1A55FFAD00377732 /* DiscoveryManager.m in Sources */, FB4678FA1A55FFAD00377732 /* DiscoveryManager.m in Sources */,
98D5856D1C0EA79600F6CC00 /* TemporaryHost.m in Sources */,
FB89463519F646E200339C8A /* MainFrameViewController.m in Sources */, FB89463519F646E200339C8A /* MainFrameViewController.m in Sources */,
FBD1C8E21A8AD71400C6703C /* Logger.m in Sources */, FBD1C8E21A8AD71400C6703C /* Logger.m in Sources */,
FB1D599A1BBCCD7E00F482CA /* AppCollectionView.m in Sources */,
FB89463619F646E200339C8A /* StreamFrameViewController.m in Sources */, FB89463619F646E200339C8A /* StreamFrameViewController.m in Sources */,
9832D1361BBCD5C50036EF48 /* TemporaryApp.m in Sources */,
98D585701C0ED0E800F6CC00 /* TemporarySettings.m in Sources */,
FB89462819F646E200339C8A /* CryptoManager.m in Sources */, FB89462819F646E200339C8A /* CryptoManager.m in Sources */,
FB89462E19F646E200339C8A /* PairManager.m in Sources */, FB89462E19F646E200339C8A /* PairManager.m in Sources */,
FB9AFD371A7E02DB00872C98 /* HttpRequest.m in Sources */, FB9AFD371A7E02DB00872C98 /* HttpRequest.m in Sources */,
@@ -986,7 +1023,7 @@
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = "Limelight/Limelight-Prefix.pch"; GCC_PREFIX_HEADER = "Limelight/Limelight-Prefix.pch";
@@ -1038,12 +1075,13 @@
FB290D0519B2C406004C83CF /* Limelight.xcdatamodeld */ = { FB290D0519B2C406004C83CF /* Limelight.xcdatamodeld */ = {
isa = XCVersionGroup; isa = XCVersionGroup;
children = ( children = (
FB53E1441BE5DCBC00CD6ECE /* Moonlight v1.0-2.xcdatamodel */,
FBB460391B50ACE400F3099C /* Moonlight v1.0.xcdatamodel */, FBB460391B50ACE400F3099C /* Moonlight v1.0.xcdatamodel */,
FB6549621A60B4A9001C8F39 /* Limelight 0.3.1.xcdatamodel */, FB6549621A60B4A9001C8F39 /* Limelight 0.3.1.xcdatamodel */,
FB4678F21A51BDCB00377732 /* Limelight 0.3.0.xcdatamodel */, FB4678F21A51BDCB00377732 /* Limelight 0.3.0.xcdatamodel */,
FB290D0619B2C406004C83CF /* Limelight.xcdatamodel */, FB290D0619B2C406004C83CF /* Limelight.xcdatamodel */,
); );
currentVersion = FBB460391B50ACE400F3099C /* Moonlight v1.0.xcdatamodel */; currentVersion = FB53E1441BE5DCBC00CD6ECE /* Moonlight v1.0-2.xcdatamodel */;
path = Limelight.xcdatamodeld; path = Limelight.xcdatamodeld;
sourceTree = "<group>"; sourceTree = "<group>";
versionGroupType = wrapper.xcdatamodel; versionGroupType = wrapper.xcdatamodel;
+8 -6
View File
@@ -1,12 +1,14 @@
#Moonlight iOS #Moonlight iOS
[Moonlight](https://github.com/moonlight-stream) is an open source implementation of NVIDIA's GameStream, as used by the NVIDIA Shield, but built for iOS. Moonlight iOS allows you to stream your full collection of Steam games from #### Now available on the [App Store](https://itunes.apple.com/us/app/moonlight-game-streaming/id1000551566?mt=8)
[Moonlight](http://moonlight-stream.com) is an open source implementation of NVIDIA's GameStream, as used by the NVIDIA Shield, but built for iOS. Moonlight iOS allows you to stream your full collection of Steam games from
your powerful desktop computer to your iOS Device. your powerful desktop computer to your iOS Device.
This is actively being developed and will be available on the App Store soon - check back here or on [our website](http://moonlight-stream.com) for updates. There are also versions for [Android](https://github.com/moonlight-stream/moonlight-android) and [PC](https://github.com/moonlight-stream/moonlight-pc).
We understand that Moonlight users are eager for the opportunity to stream their Steam collection to their iOS devices. Moonlight for iOS is in the testing stage and preliminary versions are available for beta testers. ##### Apple TV Note:
We current cannot write a port for Apple TV due to a limitation in the tvOS SDK that prevents Moonlight from using the APIs that would be necessary for it to work. Thank you for your interest - you can track this issue [here](https://github.com/moonlight-stream/moonlight-ios/issues/184).
In the meantime, check out the existing versions for [Android](https://github.com/moonlight-stream/moonlight-android) and [PC](https://github.com/moonlight-stream/moonlight-pc). Also, a client for [Windows and Windows Phone](https://github.com/moonlight-stream/moonlight-windows) is in the works and actively being developed. ##### Questions?
Check out our [wiki](https://github.com/moonlight-stream/moonlight-android/wiki).
Please be patient as we work to bring Moonlight to more platforms.
+31 -9
View File
@@ -1,7 +1,8 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="7706" systemVersion="14D136" targetRuntime="iOS.CocoaTouch.iPad" propertyAccessControl="none" initialViewController="EVd-wq-ego"> <document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="8191" systemVersion="15B38b" targetRuntime="iOS.CocoaTouch.iPad" propertyAccessControl="none" initialViewController="EVd-wq-ego">
<dependencies> <dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="7703"/> <deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="8154"/>
</dependencies> </dependencies>
<customFonts key="customFonts"> <customFonts key="customFonts">
<mutableArray key="Roboto-Regular.ttf"> <mutableArray key="Roboto-Regular.ttf">
@@ -13,9 +14,11 @@
<scene sceneID="Me4-Nr-liz"> <scene sceneID="Me4-Nr-liz">
<objects> <objects>
<viewController id="wb7-af-jn8" customClass="MainFrameViewController" sceneMemberID="viewController"> <viewController id="wb7-af-jn8" customClass="MainFrameViewController" sceneMemberID="viewController">
<collectionView key="view" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" indicatorStyle="black" dataMode="prototypes" id="TZj-Lc-M9d"> <collectionView key="view" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" indicatorStyle="black" dataMode="prototypes" id="TZj-Lc-M9d" customClass="AppCollectionView">
<rect key="frame" x="0.0" y="64" width="1024" height="704"/> <rect key="frame" x="0.0" y="64" width="1024" height="704"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<animations/>
<color key="backgroundColor" white="0.33333333333333331" alpha="1" colorSpace="calibratedWhite"/>
<inset key="contentInset" minX="40" minY="20" maxX="40" maxY="20"/> <inset key="contentInset" minX="40" minY="20" maxX="40" maxY="20"/>
<collectionViewFlowLayout key="collectionViewLayout" minimumLineSpacing="20" minimumInteritemSpacing="20" id="f7l-kG-hJc"> <collectionViewFlowLayout key="collectionViewLayout" minimumLineSpacing="20" minimumInteritemSpacing="20" id="f7l-kG-hJc">
<size key="itemSize" width="150" height="200"/> <size key="itemSize" width="150" height="200"/>
@@ -24,14 +27,16 @@
<inset key="sectionInset" minX="20" minY="20" maxX="20" maxY="20"/> <inset key="sectionInset" minX="20" minY="20" maxX="20" maxY="20"/>
</collectionViewFlowLayout> </collectionViewFlowLayout>
<cells> <cells>
<collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="AppCell" id="fv6-NS-qsK"> <collectionViewCell clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="AppCell" id="fv6-NS-qsK">
<rect key="frame" x="20" y="20" width="150" height="200"/> <rect key="frame" x="20" y="40" width="150" height="200"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center"> <view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
<rect key="frame" x="0.0" y="0.0" width="150" height="200"/> <rect key="frame" x="0.0" y="0.0" width="150" height="200"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<animations/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/> <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
</view> </view>
<animations/>
</collectionViewCell> </collectionViewCell>
</cells> </cells>
<connections> <connections>
@@ -42,14 +47,15 @@
<navigationItem key="navigationItem" id="pSu-bl-gL9"> <navigationItem key="navigationItem" id="pSu-bl-gL9">
<barButtonItem key="leftBarButtonItem" style="done" id="mSL-ru-nRm"> <barButtonItem key="leftBarButtonItem" style="done" id="mSL-ru-nRm">
<button key="customView" opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" id="Tiq-VS-Ua5"> <button key="customView" opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" id="Tiq-VS-Ua5">
<rect key="frame" x="101" y="236" width="109" height="46"/> <rect key="frame" x="20" y="-1" width="109" height="46"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<animations/>
<fontDescription key="fontDescription" name="Roboto-Regular" family="Roboto" pointSize="16"/> <fontDescription key="fontDescription" name="Roboto-Regular" family="Roboto" pointSize="16"/>
<state key="normal" title="Moonlight" image="Logo"> <state key="normal" title="Moonlight" image="Logo">
<color key="titleColor" red="0.81533776120000001" green="0.85979419950000002" blue="0.91372549020000005" alpha="1" colorSpace="calibratedRGB"/>
<color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/> <color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
</state> </state>
</button> </button>
<color key="tintColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
</barButtonItem> </barButtonItem>
<barButtonItem key="rightBarButtonItem" title="No Host Selected" id="KDy-JC-2sU"> <barButtonItem key="rightBarButtonItem" title="No Host Selected" id="KDy-JC-2sU">
<color key="tintColor" white="1" alpha="1" colorSpace="calibratedWhite"/> <color key="tintColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
@@ -77,6 +83,7 @@
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Bitrate: 10 Mbps" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="lMt-4H-fkV"> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Bitrate: 10 Mbps" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="lMt-4H-fkV">
<rect key="frame" x="16" y="39" width="151" height="21"/> <rect key="frame" x="16" y="39" width="151" height="21"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<animations/>
<color key="tintColor" red="0.81533776120000001" green="0.85979419950000002" blue="0.91372549020000005" alpha="1" colorSpace="calibratedRGB"/> <color key="tintColor" red="0.81533776120000001" green="0.85979419950000002" blue="0.91372549020000005" alpha="1" colorSpace="calibratedRGB"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/> <fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/> <color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
@@ -85,11 +92,13 @@
<slider opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" value="20" minValue="1" maxValue="100" id="JAY-nj-UNz"> <slider opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" value="20" minValue="1" maxValue="100" id="JAY-nj-UNz">
<rect key="frame" x="14" y="68" width="213" height="31"/> <rect key="frame" x="14" y="68" width="213" height="31"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<animations/>
<color key="tintColor" red="0.72941176470000002" green="0.69411764710000001" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="tintColor" red="0.72941176470000002" green="0.69411764710000001" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</slider> </slider>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Framerate" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Aiv-r8-9k2"> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Framerate" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Aiv-r8-9k2">
<rect key="frame" x="16" y="106" width="79" height="21"/> <rect key="frame" x="16" y="106" width="79" height="21"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<animations/>
<color key="tintColor" red="0.81533776120000001" green="0.85979419950000002" blue="0.91372549020000005" alpha="1" colorSpace="calibratedRGB"/> <color key="tintColor" red="0.81533776120000001" green="0.85979419950000002" blue="0.91372549020000005" alpha="1" colorSpace="calibratedRGB"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/> <fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/> <color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
@@ -98,6 +107,7 @@
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Resolution" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="pYZ-GR-EwO"> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Resolution" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="pYZ-GR-EwO">
<rect key="frame" x="16" y="171" width="82" height="21"/> <rect key="frame" x="16" y="171" width="82" height="21"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<animations/>
<color key="tintColor" red="0.81533776120000001" green="0.85979419950000002" blue="0.91372549020000005" alpha="1" colorSpace="calibratedRGB"/> <color key="tintColor" red="0.81533776120000001" green="0.85979419950000002" blue="0.91372549020000005" alpha="1" colorSpace="calibratedRGB"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/> <fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/> <color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
@@ -106,16 +116,17 @@
<segmentedControl opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="0" id="ckc-Dm-8ex"> <segmentedControl opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="0" id="ckc-Dm-8ex">
<rect key="frame" x="16" y="200" width="209" height="29"/> <rect key="frame" x="16" y="200" width="209" height="29"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<animations/>
<segments> <segments>
<segment title="720p"/> <segment title="720p"/>
<segment title="1080p"/> <segment title="1080p"/>
<segment title="Native"/>
</segments> </segments>
<color key="tintColor" red="0.72941176470000002" green="0.69411764710000001" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="tintColor" red="0.72941176470000002" green="0.69411764710000001" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</segmentedControl> </segmentedControl>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="On-Screen Controls" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="LGV-5y-piG"> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="On-Screen Controls" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="LGV-5y-piG">
<rect key="frame" x="16" y="236" width="153" height="21"/> <rect key="frame" x="16" y="236" width="153" height="21"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<animations/>
<color key="tintColor" red="0.81533776120000001" green="0.85979419950000002" blue="0.91372549020000005" alpha="1" colorSpace="calibratedRGB"/> <color key="tintColor" red="0.81533776120000001" green="0.85979419950000002" blue="0.91372549020000005" alpha="1" colorSpace="calibratedRGB"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/> <fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/> <color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
@@ -124,6 +135,7 @@
<segmentedControl opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="1" id="qSU-wh-tqA"> <segmentedControl opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="1" id="qSU-wh-tqA">
<rect key="frame" x="16" y="265" width="209" height="29"/> <rect key="frame" x="16" y="265" width="209" height="29"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<animations/>
<segments> <segments>
<segment title="Off"/> <segment title="Off"/>
<segment title="Auto"/> <segment title="Auto"/>
@@ -135,6 +147,7 @@
<segmentedControl opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="1" id="lGK-vl-pdw"> <segmentedControl opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="1" id="lGK-vl-pdw">
<rect key="frame" x="16" y="135" width="209" height="29"/> <rect key="frame" x="16" y="135" width="209" height="29"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<animations/>
<segments> <segments>
<segment title="30 Hz"/> <segment title="30 Hz"/>
<segment title="60 Hz"/> <segment title="60 Hz"/>
@@ -142,6 +155,7 @@
<color key="tintColor" red="0.72941176470000002" green="0.69411764710000001" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="tintColor" red="0.72941176470000002" green="0.69411764710000001" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</segmentedControl> </segmentedControl>
</subviews> </subviews>
<animations/>
<color key="backgroundColor" cocoaTouchSystemColor="viewFlipsideBackgroundColor"/> <color key="backgroundColor" cocoaTouchSystemColor="viewFlipsideBackgroundColor"/>
</view> </view>
<simulatedOrientationMetrics key="simulatedOrientationMetrics" orientation="landscapeRight"/> <simulatedOrientationMetrics key="simulatedOrientationMetrics" orientation="landscapeRight"/>
@@ -164,6 +178,7 @@
<view key="view" contentMode="scaleToFill" id="hGs-BR-t4b"> <view key="view" contentMode="scaleToFill" id="hGs-BR-t4b">
<rect key="frame" x="0.0" y="0.0" width="1024" height="768"/> <rect key="frame" x="0.0" y="0.0" width="1024" height="768"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<animations/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/> <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
</view> </view>
<simulatedOrientationMetrics key="simulatedOrientationMetrics" orientation="landscapeRight"/> <simulatedOrientationMetrics key="simulatedOrientationMetrics" orientation="landscapeRight"/>
@@ -184,6 +199,8 @@
<navigationBar key="navigationBar" contentMode="scaleToFill" barStyle="black" translucent="NO" id="RUe-14-4Ya"> <navigationBar key="navigationBar" contentMode="scaleToFill" barStyle="black" translucent="NO" id="RUe-14-4Ya">
<rect key="frame" x="0.0" y="0.0" width="768" height="44"/> <rect key="frame" x="0.0" y="0.0" width="768" height="44"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<animations/>
<color key="barTintColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
</navigationBar> </navigationBar>
<connections> <connections>
<segue destination="wb7-af-jn8" kind="relationship" relationship="rootViewController" id="nkY-JG-Wki"/> <segue destination="wb7-af-jn8" kind="relationship" relationship="rootViewController" id="nkY-JG-Wki"/>
@@ -204,16 +221,19 @@
<activityIndicatorView opaque="NO" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" hidesWhenStopped="YES" animating="YES" style="white" id="iOs-1X-mSU"> <activityIndicatorView opaque="NO" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" hidesWhenStopped="YES" animating="YES" style="white" id="iOs-1X-mSU">
<rect key="frame" x="502" y="374" width="20" height="20"/> <rect key="frame" x="502" y="374" width="20" height="20"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<animations/>
</activityIndicatorView> </activityIndicatorView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="dDs-kT-po6"> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="dDs-kT-po6">
<rect key="frame" x="491" y="402" width="42" height="21"/> <rect key="frame" x="491" y="402" width="42" height="21"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<animations/>
<fontDescription key="fontDescription" type="system" pointSize="17"/> <fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="calibratedRGB"/> <color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="calibratedRGB"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
</subviews> </subviews>
<color key="backgroundColor" white="0.0" alpha="1" colorSpace="calibratedWhite"/> <animations/>
<color key="backgroundColor" white="0.33333333333333331" alpha="1" colorSpace="calibratedWhite"/>
</view> </view>
<navigationItem key="navigationItem" id="iUf-9X-GeA"/> <navigationItem key="navigationItem" id="iUf-9X-GeA"/>
<nil key="simulatedStatusBarMetrics"/> <nil key="simulatedStatusBarMetrics"/>
@@ -241,8 +261,10 @@
<activityIndicatorView opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" animating="YES" style="whiteLarge" id="N0D-dj-EuF"> <activityIndicatorView opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" animating="YES" style="whiteLarge" id="N0D-dj-EuF">
<rect key="frame" x="494" y="365" width="37" height="37"/> <rect key="frame" x="494" y="365" width="37" height="37"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<animations/>
</activityIndicatorView> </activityIndicatorView>
</subviews> </subviews>
<animations/>
<color key="backgroundColor" white="0.0" alpha="0.5" colorSpace="calibratedWhite"/> <color key="backgroundColor" white="0.0" alpha="0.5" colorSpace="calibratedWhite"/>
</view> </view>
<simulatedOrientationMetrics key="simulatedOrientationMetrics" orientation="landscapeRight"/> <simulatedOrientationMetrics key="simulatedOrientationMetrics" orientation="landscapeRight"/>
+30 -13
View File
@@ -1,7 +1,8 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="7706" systemVersion="14D136" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" initialViewController="DL0-L5-LOv"> <document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="8191" systemVersion="15B38b" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" initialViewController="DL0-L5-LOv">
<dependencies> <dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="7703"/> <deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="8154"/>
</dependencies> </dependencies>
<customFonts key="customFonts"> <customFonts key="customFonts">
<mutableArray key="Roboto-Regular.ttf"> <mutableArray key="Roboto-Regular.ttf">
@@ -13,9 +14,11 @@
<scene sceneID="emz-kO-L7q"> <scene sceneID="emz-kO-L7q">
<objects> <objects>
<viewController id="dgh-JZ-Q7z" customClass="MainFrameViewController" sceneMemberID="viewController"> <viewController id="dgh-JZ-Q7z" customClass="MainFrameViewController" sceneMemberID="viewController">
<collectionView key="view" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" indicatorStyle="black" dataMode="prototypes" id="Rtu-AT-Alw"> <collectionView key="view" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" dataMode="prototypes" id="Rtu-AT-Alw" customClass="AppCollectionView">
<rect key="frame" x="0.0" y="64" width="568" height="256"/> <rect key="frame" x="0.0" y="64" width="568" height="256"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<animations/>
<color key="backgroundColor" white="0.33333333333333331" alpha="1" colorSpace="calibratedWhite"/>
<inset key="contentInset" minX="40" minY="20" maxX="40" maxY="20"/> <inset key="contentInset" minX="40" minY="20" maxX="40" maxY="20"/>
<collectionViewFlowLayout key="collectionViewLayout" minimumLineSpacing="20" minimumInteritemSpacing="20" id="YcZ-cR-3tK"> <collectionViewFlowLayout key="collectionViewLayout" minimumLineSpacing="20" minimumInteritemSpacing="20" id="YcZ-cR-3tK">
<size key="itemSize" width="90" height="120"/> <size key="itemSize" width="90" height="120"/>
@@ -24,14 +27,16 @@
<inset key="sectionInset" minX="20" minY="20" maxX="20" maxY="20"/> <inset key="sectionInset" minX="20" minY="20" maxX="20" maxY="20"/>
</collectionViewFlowLayout> </collectionViewFlowLayout>
<cells> <cells>
<collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="AppCell" id="Uqv-Di-fzX"> <collectionViewCell clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="AppCell" id="Uqv-Di-fzX">
<rect key="frame" x="20" y="20" width="90" height="120"/> <rect key="frame" x="20" y="40" width="90" height="120"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center"> <view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
<rect key="frame" x="0.0" y="0.0" width="90" height="120"/> <rect key="frame" x="0.0" y="0.0" width="90" height="120"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<animations/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/> <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
</view> </view>
<animations/>
</collectionViewCell> </collectionViewCell>
</cells> </cells>
<connections> <connections>
@@ -43,8 +48,9 @@
<navigationItem key="navigationItem" id="1jn-Sf-Xky"> <navigationItem key="navigationItem" id="1jn-Sf-Xky">
<barButtonItem key="leftBarButtonItem" style="done" id="zAn-CM-7Yz"> <barButtonItem key="leftBarButtonItem" style="done" id="zAn-CM-7Yz">
<button key="customView" opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" id="pZ9-ft-T24"> <button key="customView" opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" id="pZ9-ft-T24">
<rect key="frame" x="0.0" y="0.0" width="109" height="46"/> <rect key="frame" x="20" y="-1" width="109" height="46"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<animations/>
<fontDescription key="fontDescription" name="Roboto-Regular" family="Roboto" pointSize="16"/> <fontDescription key="fontDescription" name="Roboto-Regular" family="Roboto" pointSize="16"/>
<state key="normal" title="Moonlight" image="Logo"> <state key="normal" title="Moonlight" image="Logo">
<color key="titleColor" red="0.96078431372549022" green="0.98039215686274506" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="titleColor" red="0.96078431372549022" green="0.98039215686274506" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
@@ -74,6 +80,7 @@
<view key="view" contentMode="scaleToFill" id="Usg-e0-g4a"> <view key="view" contentMode="scaleToFill" id="Usg-e0-g4a">
<rect key="frame" x="0.0" y="0.0" width="568" height="320"/> <rect key="frame" x="0.0" y="0.0" width="568" height="320"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<animations/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/> <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
</view> </view>
<simulatedOrientationMetrics key="simulatedOrientationMetrics" orientation="landscapeRight"/> <simulatedOrientationMetrics key="simulatedOrientationMetrics" orientation="landscapeRight"/>
@@ -97,11 +104,13 @@
<slider opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" value="20" minValue="1" maxValue="100" id="3nn-MI-9Xu"> <slider opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" value="20" minValue="1" maxValue="100" id="3nn-MI-9Xu">
<rect key="frame" x="14" y="49" width="204" height="31"/> <rect key="frame" x="14" y="49" width="204" height="31"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<animations/>
<color key="tintColor" red="0.72941176470588232" green="0.69411764705882351" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="tintColor" red="0.72941176470588232" green="0.69411764705882351" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</slider> </slider>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Bitrate: 10 Mbps" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="SBv-Wn-LB7"> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Bitrate: 10 Mbps" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="SBv-Wn-LB7">
<rect key="frame" x="16" y="20" width="151" height="21"/> <rect key="frame" x="16" y="20" width="151" height="21"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<animations/>
<fontDescription key="fontDescription" type="system" pointSize="17"/> <fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.96078431372549022" green="0.98039215686274506" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="textColor" red="0.96078431372549022" green="0.98039215686274506" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
@@ -109,6 +118,7 @@
<segmentedControl opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="1" id="dLF-qJ-2nY"> <segmentedControl opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="1" id="dLF-qJ-2nY">
<rect key="frame" x="16" y="116" width="200" height="29"/> <rect key="frame" x="16" y="116" width="200" height="29"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<animations/>
<segments> <segments>
<segment title="30 Hz"/> <segment title="30 Hz"/>
<segment title="60 Hz"/> <segment title="60 Hz"/>
@@ -118,6 +128,7 @@
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Framerate" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Kkv-DF-MAl"> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Framerate" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Kkv-DF-MAl">
<rect key="frame" x="16" y="87" width="79" height="21"/> <rect key="frame" x="16" y="87" width="79" height="21"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<animations/>
<fontDescription key="fontDescription" type="system" pointSize="17"/> <fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.96078431372549022" green="0.98039215686274506" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="textColor" red="0.96078431372549022" green="0.98039215686274506" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
@@ -125,6 +136,7 @@
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Resolution" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="8zy-ri-Dqc"> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Resolution" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="8zy-ri-Dqc">
<rect key="frame" x="16" y="152" width="82" height="21"/> <rect key="frame" x="16" y="152" width="82" height="21"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<animations/>
<fontDescription key="fontDescription" type="system" pointSize="17"/> <fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.96078431372549022" green="0.98039215686274506" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="textColor" red="0.96078431372549022" green="0.98039215686274506" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
@@ -132,16 +144,17 @@
<segmentedControl opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="0" id="PCM-t4-Sha"> <segmentedControl opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="0" id="PCM-t4-Sha">
<rect key="frame" x="16" y="181" width="200" height="29"/> <rect key="frame" x="16" y="181" width="200" height="29"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<animations/>
<segments> <segments>
<segment title="720p"/> <segment title="720p"/>
<segment title="1080p"/> <segment title="1080p"/>
<segment title="Native"/>
</segments> </segments>
<color key="tintColor" red="0.72941176470588232" green="0.69411764705882351" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="tintColor" red="0.72941176470588232" green="0.69411764705882351" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</segmentedControl> </segmentedControl>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="On-Screen Controls" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="HZT-dd-DuQ"> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="On-Screen Controls" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="HZT-dd-DuQ">
<rect key="frame" x="16" y="217" width="153" height="21"/> <rect key="frame" x="16" y="217" width="153" height="21"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<animations/>
<fontDescription key="fontDescription" type="system" pointSize="17"/> <fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.96078431372549022" green="0.98039215686274506" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="textColor" red="0.96078431372549022" green="0.98039215686274506" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
@@ -149,6 +162,7 @@
<segmentedControl opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="1" id="WGf-9d-eAm"> <segmentedControl opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="1" id="WGf-9d-eAm">
<rect key="frame" x="16" y="246" width="200" height="29"/> <rect key="frame" x="16" y="246" width="200" height="29"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<animations/>
<segments> <segments>
<segment title="Off"/> <segment title="Off"/>
<segment title="Auto"/> <segment title="Auto"/>
@@ -158,6 +172,7 @@
<color key="tintColor" red="0.72941176470588232" green="0.69411764705882351" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="tintColor" red="0.72941176470588232" green="0.69411764705882351" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</segmentedControl> </segmentedControl>
</subviews> </subviews>
<animations/>
<color key="backgroundColor" red="0.12156862745098039" green="0.12941176470588237" blue="0.14117647058823529" alpha="1" colorSpace="calibratedRGB"/> <color key="backgroundColor" red="0.12156862745098039" green="0.12941176470588237" blue="0.14117647058823529" alpha="1" colorSpace="calibratedRGB"/>
</view> </view>
<simulatedOrientationMetrics key="simulatedOrientationMetrics" orientation="landscapeRight"/> <simulatedOrientationMetrics key="simulatedOrientationMetrics" orientation="landscapeRight"/>
@@ -181,6 +196,8 @@
<navigationBar key="navigationBar" contentMode="scaleToFill" barStyle="black" translucent="NO" id="0ZA-Ec-QgD"> <navigationBar key="navigationBar" contentMode="scaleToFill" barStyle="black" translucent="NO" id="0ZA-Ec-QgD">
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/> <rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<animations/>
<color key="barTintColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
<textAttributes key="titleTextAttributes"> <textAttributes key="titleTextAttributes">
<offsetWrapper key="textShadowOffset" horizontal="0.0" vertical="0.0"/> <offsetWrapper key="textShadowOffset" horizontal="0.0" vertical="0.0"/>
</textAttributes> </textAttributes>
@@ -204,6 +221,7 @@
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Stage" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="2HK-Z5-4Ch"> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Stage" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="2HK-Z5-4Ch">
<rect key="frame" x="262" y="149" width="45" height="21"/> <rect key="frame" x="262" y="149" width="45" height="21"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<animations/>
<fontDescription key="fontDescription" type="system" pointSize="17"/> <fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="calibratedRGB"/> <color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="calibratedRGB"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
@@ -211,9 +229,11 @@
<activityIndicatorView opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" hidesWhenStopped="YES" animating="YES" style="white" id="0vm-Iv-K4b"> <activityIndicatorView opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" hidesWhenStopped="YES" animating="YES" style="white" id="0vm-Iv-K4b">
<rect key="frame" x="274" y="121" width="20" height="20"/> <rect key="frame" x="274" y="121" width="20" height="20"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<animations/>
</activityIndicatorView> </activityIndicatorView>
</subviews> </subviews>
<color key="backgroundColor" white="0.0" alpha="1" colorSpace="calibratedWhite"/> <animations/>
<color key="backgroundColor" white="0.33333333333333331" alpha="1" colorSpace="calibratedWhite"/>
</view> </view>
<navigationItem key="navigationItem" id="8uX-4T-BiC"/> <navigationItem key="navigationItem" id="8uX-4T-BiC"/>
<nil key="simulatedStatusBarMetrics"/> <nil key="simulatedStatusBarMetrics"/>
@@ -239,8 +259,10 @@
<subviews> <subviews>
<activityIndicatorView opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" animating="YES" style="whiteLarge" translatesAutoresizingMaskIntoConstraints="NO" id="oNu-Gu-QeM"> <activityIndicatorView opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" animating="YES" style="whiteLarge" translatesAutoresizingMaskIntoConstraints="NO" id="oNu-Gu-QeM">
<rect key="frame" x="266" y="141" width="37" height="37"/> <rect key="frame" x="266" y="141" width="37" height="37"/>
<animations/>
</activityIndicatorView> </activityIndicatorView>
</subviews> </subviews>
<animations/>
<color key="backgroundColor" white="0.0" alpha="0.5" colorSpace="calibratedWhite"/> <color key="backgroundColor" white="0.0" alpha="0.5" colorSpace="calibratedWhite"/>
</view> </view>
<simulatedOrientationMetrics key="simulatedOrientationMetrics" orientation="landscapeRight"/> <simulatedOrientationMetrics key="simulatedOrientationMetrics" orientation="landscapeRight"/>
@@ -256,9 +278,4 @@
<resources> <resources>
<image name="Logo" width="32" height="32"/> <image name="Logo" width="32" height="32"/>
</resources> </resources>
<simulatedMetricsContainer key="defaultSimulatedMetrics">
<simulatedStatusBarMetrics key="statusBar"/>
<simulatedOrientationMetrics key="orientation"/>
<simulatedScreenMetrics key="destination" type="retina4"/>
</simulatedMetricsContainer>
</document> </document>