further renaming
@@ -1,13 +0,0 @@
|
||||
//
|
||||
// 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
|
||||
@@ -1,28 +0,0 @@
|
||||
//
|
||||
// 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
|
||||
@@ -1,25 +0,0 @@
|
||||
//
|
||||
// AppDelegate.h
|
||||
// Moonlight
|
||||
//
|
||||
// Created by Diego Waxemberg on 1/17/14.
|
||||
// Copyright (c) 2014 Moonlight Stream. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface AppDelegate : UIResponder <UIApplicationDelegate>
|
||||
|
||||
@property (strong, nonatomic) UIWindow *window;
|
||||
@property (strong, nonatomic) NSString *pcUuidToLoad;
|
||||
@property (strong, nonatomic) void (^shortcutCompletionHandler)(BOOL);
|
||||
|
||||
@property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext;
|
||||
@property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel;
|
||||
@property (readonly, strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator;
|
||||
|
||||
- (void)saveContext;
|
||||
- (NSURL *)applicationDocumentsDirectory;
|
||||
- (NSURL*) getStoreURL;
|
||||
|
||||
@end
|
||||
@@ -1,236 +0,0 @@
|
||||
//
|
||||
// AppDelegate.m
|
||||
// Moonlight
|
||||
//
|
||||
// Created by Diego Waxemberg on 1/17/14.
|
||||
// Copyright (c) 2014 Moonlight Stream. All rights reserved.
|
||||
//
|
||||
// Modified by True砖家 since 2024.7.12
|
||||
// Copyright © 2024 True砖家 @ Bilibili. All rights reserved.
|
||||
//
|
||||
|
||||
#import "AppDelegate.h"
|
||||
#import "MainFrameViewController.h"
|
||||
#import "Moonlight-Swift.h"
|
||||
|
||||
@implementation AppDelegate
|
||||
|
||||
@synthesize managedObjectContext = _managedObjectContext;
|
||||
@synthesize managedObjectModel = _managedObjectModel;
|
||||
@synthesize persistentStoreCoordinator = _persistentStoreCoordinator;
|
||||
|
||||
static NSOperationQueue* mainQueue;
|
||||
|
||||
#if TARGET_OS_TV
|
||||
static NSString* DB_NAME = @"Moonlight_tvOS.bin";
|
||||
#else
|
||||
static NSString* DB_NAME = @"Limelight_iOS.sqlite";
|
||||
#endif
|
||||
|
||||
|
||||
#if !TARGET_OS_TV
|
||||
|
||||
/*
|
||||
// orietation limitatioin test
|
||||
- (UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window {
|
||||
UIViewController *topController = window.rootViewController;
|
||||
while (topController.presentedViewController) {
|
||||
topController = topController.presentedViewController;
|
||||
}
|
||||
return [topController supportedInterfaceOrientations];
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
- (UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window {
|
||||
NSLog(@"orientation limit");
|
||||
return UIInterfaceOrientationMaskLandscape;
|
||||
}
|
||||
*/
|
||||
|
||||
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
|
||||
// Override point for command tool customization after application launch (works only when user default is nil)
|
||||
[CommandManager presetDefaultCommands];
|
||||
return YES;
|
||||
}
|
||||
|
||||
|
||||
- (void)application:(UIApplication *)application performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem completionHandler:(void (^)(BOOL succeeded))completionHandler {
|
||||
_pcUuidToLoad = (NSString*)[shortcutItem.userInfo objectForKey:@"UUID"];
|
||||
_shortcutCompletionHandler = completionHandler;
|
||||
}
|
||||
#endif
|
||||
|
||||
- (void)applicationWillResignActive:(UIApplication *)application
|
||||
{
|
||||
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
|
||||
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
|
||||
}
|
||||
|
||||
- (void)applicationDidEnterBackground:(UIApplication *)application
|
||||
{
|
||||
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
|
||||
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
|
||||
}
|
||||
|
||||
- (void)applicationWillEnterForeground:(UIApplication *)application
|
||||
{
|
||||
// Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
|
||||
}
|
||||
|
||||
- (void)applicationDidBecomeActive:(UIApplication *)application
|
||||
{
|
||||
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
|
||||
}
|
||||
|
||||
- (void)applicationWillTerminate:(UIApplication *)application
|
||||
{
|
||||
// Saves changes in the application's managed object context before the application terminates.
|
||||
[self saveContext];
|
||||
}
|
||||
|
||||
- (void)saveContext
|
||||
{
|
||||
NSManagedObjectContext *managedObjectContext = [self managedObjectContext];
|
||||
if (managedObjectContext != nil) {
|
||||
[managedObjectContext performBlock:^{
|
||||
if (![managedObjectContext hasChanges]) {
|
||||
return;
|
||||
}
|
||||
NSError *error = nil;
|
||||
if (![managedObjectContext save:&error]) {
|
||||
Log(LOG_E, @"Critical database error: %@, %@", error, [error userInfo]);
|
||||
}
|
||||
|
||||
#if TARGET_OS_TV
|
||||
NSData* dbData = [NSData dataWithContentsOfURL:[[[[NSFileManager defaultManager] URLsForDirectory:NSCachesDirectory inDomains:NSUserDomainMask] lastObject] URLByAppendingPathComponent:DB_NAME]];
|
||||
[[NSUserDefaults standardUserDefaults] setObject:dbData forKey:DB_NAME];
|
||||
#endif
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Core Data stack
|
||||
|
||||
// Returns the managed object context for the application.
|
||||
// If the context doesn't already exist, it is created and bound to the persistent store coordinator for the application.
|
||||
- (NSManagedObjectContext *)managedObjectContext
|
||||
{
|
||||
if (_managedObjectContext != nil) {
|
||||
return _managedObjectContext;
|
||||
}
|
||||
|
||||
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
|
||||
if (coordinator != nil) {
|
||||
_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
|
||||
[_managedObjectContext setPersistentStoreCoordinator:coordinator];
|
||||
}
|
||||
return _managedObjectContext;
|
||||
}
|
||||
|
||||
// Returns the managed object model for the application.
|
||||
// If the model doesn't already exist, it is created from the application's model.
|
||||
- (NSManagedObjectModel *)managedObjectModel
|
||||
{
|
||||
if (_managedObjectModel != nil) {
|
||||
return _managedObjectModel;
|
||||
}
|
||||
_managedObjectModel = [NSManagedObjectModel mergedModelFromBundles:nil];
|
||||
return _managedObjectModel;
|
||||
}
|
||||
|
||||
// Returns the persistent store coordinator for the application.
|
||||
// If the coordinator doesn't already exist, it is created and the application's store added to it.
|
||||
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
|
||||
{
|
||||
if (_persistentStoreCoordinator != nil) {
|
||||
return _persistentStoreCoordinator;
|
||||
}
|
||||
|
||||
NSError *error = nil;
|
||||
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
|
||||
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
|
||||
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
|
||||
NSString* storeType;
|
||||
|
||||
#if TARGET_OS_TV
|
||||
// Use a binary store for tvOS since we will need exclusive access to the file
|
||||
// to serialize into NSUserDefaults.
|
||||
storeType = NSBinaryStoreType;
|
||||
#else
|
||||
storeType = NSSQLiteStoreType;
|
||||
#endif
|
||||
|
||||
// We must ensure the persistent store is ready to opened
|
||||
[self preparePersistentStore];
|
||||
|
||||
if (![_persistentStoreCoordinator addPersistentStoreWithType:storeType configuration:nil URL:[self getStoreURL] options:options error:&error]) {
|
||||
// Log the error
|
||||
Log(LOG_E, @"Critical database error: %@, %@", error, [error userInfo]);
|
||||
|
||||
// Drop the database
|
||||
[self dropDatabase];
|
||||
|
||||
// Try again
|
||||
return [self persistentStoreCoordinator];
|
||||
}
|
||||
|
||||
return _persistentStoreCoordinator;
|
||||
}
|
||||
|
||||
#pragma mark - Application's Documents directory
|
||||
|
||||
// Returns the URL to the application's Documents directory.
|
||||
- (NSURL *)applicationDocumentsDirectory
|
||||
{
|
||||
return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
|
||||
}
|
||||
|
||||
- (void) dropDatabase
|
||||
{
|
||||
// Delete the file on disk
|
||||
[[NSFileManager defaultManager] removeItemAtURL:[self getStoreURL] error:nil];
|
||||
|
||||
#if TARGET_OS_TV
|
||||
// Also delete the copy in the NSUserDefaults on tvOS
|
||||
[[NSUserDefaults standardUserDefaults] removeObjectForKey:DB_NAME];
|
||||
#endif
|
||||
}
|
||||
|
||||
- (void) preparePersistentStore
|
||||
{
|
||||
#if TARGET_OS_TV
|
||||
// On tvOS, we may need to inflate the DB from NSUserDefaults
|
||||
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
|
||||
NSString *cacheDirectory = [paths objectAtIndex:0];
|
||||
NSString *dbPath = [cacheDirectory stringByAppendingPathComponent:DB_NAME];
|
||||
|
||||
// Always prefer the on disk version
|
||||
if (![[NSFileManager defaultManager] fileExistsAtPath:dbPath]) {
|
||||
// If that is unavailable, inflate it from NSUserDefaults
|
||||
NSData* data = [[NSUserDefaults standardUserDefaults] dataForKey:DB_NAME];
|
||||
if (data != nil) {
|
||||
Log(LOG_I, @"Inflating database from NSUserDefaults");
|
||||
[data writeToFile:dbPath atomically:YES];
|
||||
}
|
||||
else {
|
||||
Log(LOG_I, @"No database on disk or in NSUserDefaults");
|
||||
}
|
||||
}
|
||||
else {
|
||||
Log(LOG_I, @"Using cached database");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
- (NSURL*) getStoreURL {
|
||||
#if TARGET_OS_TV
|
||||
// We use the cache folder to store our database on tvOS
|
||||
return [[[[NSFileManager defaultManager] URLsForDirectory:NSCachesDirectory inDomains:NSUserDomainMask] lastObject] URLByAppendingPathComponent:DB_NAME];
|
||||
#else
|
||||
return [[self applicationDocumentsDirectory] URLByAppendingPathComponent:DB_NAME];
|
||||
#endif
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,13 +0,0 @@
|
||||
//
|
||||
// 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
|
||||
@@ -1,28 +0,0 @@
|
||||
//
|
||||
// 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,27 +0,0 @@
|
||||
//
|
||||
// CryptoManager.h
|
||||
// Moonlight
|
||||
//
|
||||
// Created by Diego Waxemberg on 10/14/14.
|
||||
// Copyright (c) 2014 Moonlight Stream. All rights reserved.
|
||||
//
|
||||
|
||||
@interface CryptoManager : NSObject
|
||||
|
||||
+ (void) generateKeyPairUsingSSL;
|
||||
+ (NSData*) readCertFromFile;
|
||||
+ (NSData*) readKeyFromFile;
|
||||
+ (NSData*) readP12FromFile;
|
||||
+ (NSData*) getSignatureFromCert:(NSData*)cert;
|
||||
+ (NSData*) pemToDer:(NSData*)pemCertBytes;
|
||||
|
||||
- (NSData*) createAESKeyFromSaltSHA1:(NSData*)saltedPIN;
|
||||
- (NSData*) createAESKeyFromSaltSHA256:(NSData*)saltedPIN;
|
||||
- (NSData*) SHA1HashData:(NSData*)data;
|
||||
- (NSData*) SHA256HashData:(NSData*)data;
|
||||
- (NSData*) aesEncrypt:(NSData*)data withKey:(NSData*)key;
|
||||
- (NSData*) aesDecrypt:(NSData*)data withKey:(NSData*)key;
|
||||
- (bool) verifySignature:(NSData *)data withSignature:(NSData*)signature andCert:(NSData*)cert;
|
||||
- (NSData*) signData:(NSData*)data withKey:(NSData*)key;
|
||||
|
||||
@end
|
||||
@@ -1,308 +0,0 @@
|
||||
//
|
||||
// CryptoManager.m
|
||||
// Moonlight
|
||||
//
|
||||
// Created by Diego Waxemberg on 10/14/14.
|
||||
// Copyright (c) 2014 Moonlight Stream. All rights reserved.
|
||||
//
|
||||
|
||||
#import "CryptoManager.h"
|
||||
#import "mkcert.h"
|
||||
|
||||
#include <openssl/sha.h>
|
||||
#include <openssl/x509.h>
|
||||
#include <openssl/pem.h>
|
||||
#include <openssl/evp.h>
|
||||
|
||||
@implementation CryptoManager
|
||||
static const int SHA1_HASH_LENGTH = 20;
|
||||
static const int SHA256_HASH_LENGTH = 32;
|
||||
static NSData* key = nil;
|
||||
static NSData* cert = nil;
|
||||
static NSData* p12 = nil;
|
||||
|
||||
- (NSData*) createAESKeyFromSaltSHA1:(NSData*)saltedPIN {
|
||||
return [[self SHA1HashData:saltedPIN] subdataWithRange:NSMakeRange(0, 16)];
|
||||
}
|
||||
|
||||
- (NSData*) createAESKeyFromSaltSHA256:(NSData*)saltedPIN {
|
||||
return [[self SHA256HashData:saltedPIN] subdataWithRange:NSMakeRange(0, 16)];
|
||||
}
|
||||
|
||||
- (NSData*) SHA1HashData:(NSData*)data {
|
||||
unsigned char sha1[SHA1_HASH_LENGTH];
|
||||
SHA1([data bytes], [data length], sha1);
|
||||
NSData* bytes = [NSData dataWithBytes:sha1 length:sizeof(sha1)];
|
||||
return bytes;
|
||||
}
|
||||
|
||||
- (NSData*) SHA256HashData:(NSData*)data {
|
||||
unsigned char sha256[SHA256_HASH_LENGTH];
|
||||
SHA256([data bytes], [data length], sha256);
|
||||
NSData* bytes = [NSData dataWithBytes:sha256 length:sizeof(sha256)];
|
||||
return bytes;
|
||||
}
|
||||
|
||||
- (NSData*) aesEncrypt:(NSData*)data withKey:(NSData*)key {
|
||||
EVP_CIPHER_CTX* cipher;
|
||||
int ciphertextLen;
|
||||
|
||||
cipher = EVP_CIPHER_CTX_new();
|
||||
|
||||
EVP_EncryptInit(cipher, EVP_aes_128_ecb(), [key bytes], NULL);
|
||||
EVP_CIPHER_CTX_set_padding(cipher, 0);
|
||||
|
||||
NSMutableData* ciphertext = [NSMutableData dataWithLength:[data length]];
|
||||
EVP_EncryptUpdate(cipher,
|
||||
[ciphertext mutableBytes],
|
||||
&ciphertextLen,
|
||||
[data bytes],
|
||||
(int)[data length]);
|
||||
assert(ciphertextLen == [ciphertext length]);
|
||||
|
||||
EVP_CIPHER_CTX_free(cipher);
|
||||
|
||||
return ciphertext;
|
||||
}
|
||||
|
||||
- (NSData*) aesDecrypt:(NSData*)data withKey:(NSData*)key {
|
||||
EVP_CIPHER_CTX* cipher;
|
||||
int plaintextLen;
|
||||
|
||||
cipher = EVP_CIPHER_CTX_new();
|
||||
|
||||
EVP_DecryptInit(cipher, EVP_aes_128_ecb(), [key bytes], NULL);
|
||||
EVP_CIPHER_CTX_set_padding(cipher, 0);
|
||||
|
||||
NSMutableData* plaintext = [NSMutableData dataWithLength:[data length]];
|
||||
EVP_DecryptUpdate(cipher,
|
||||
[plaintext mutableBytes],
|
||||
&plaintextLen,
|
||||
[data bytes],
|
||||
(int)[data length]);
|
||||
assert(plaintextLen == [plaintext length]);
|
||||
|
||||
EVP_CIPHER_CTX_free(cipher);
|
||||
|
||||
return plaintext;
|
||||
}
|
||||
|
||||
+ (NSData*) pemToDer:(NSData*)pemCertBytes {
|
||||
X509* x509;
|
||||
|
||||
BIO* bio = BIO_new_mem_buf([pemCertBytes bytes], (int)[pemCertBytes length]);
|
||||
x509 = PEM_read_bio_X509(bio, NULL, NULL, NULL);
|
||||
BIO_free(bio);
|
||||
|
||||
bio = BIO_new(BIO_s_mem());
|
||||
i2d_X509_bio(bio, x509);
|
||||
X509_free(x509);
|
||||
|
||||
BUF_MEM* mem;
|
||||
BIO_get_mem_ptr(bio, &mem);
|
||||
|
||||
NSData* ret = [[NSData alloc] initWithBytes:mem->data length:mem->length];
|
||||
BIO_free(bio);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
- (bool) verifySignature:(NSData *)data withSignature:(NSData*)signature andCert:(NSData*)cert {
|
||||
X509* x509;
|
||||
BIO* bio = BIO_new_mem_buf([cert bytes], (int)[cert length]);
|
||||
x509 = PEM_read_bio_X509(bio, NULL, NULL, NULL);
|
||||
|
||||
BIO_free(bio);
|
||||
|
||||
if (!x509) {
|
||||
Log(LOG_E, @"Unable to parse certificate in memory");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
EVP_PKEY* pubKey = X509_get_pubkey(x509);
|
||||
EVP_MD_CTX *mdctx = NULL;
|
||||
mdctx = EVP_MD_CTX_create();
|
||||
EVP_DigestVerifyInit(mdctx, NULL, EVP_sha256(), NULL, pubKey);
|
||||
EVP_DigestVerifyUpdate(mdctx, [data bytes], [data length]);
|
||||
int result = EVP_DigestVerifyFinal(mdctx, (unsigned char*)[signature bytes], [signature length]);
|
||||
|
||||
X509_free(x509);
|
||||
EVP_PKEY_free(pubKey);
|
||||
EVP_MD_CTX_destroy(mdctx);
|
||||
|
||||
return result > 0;
|
||||
}
|
||||
|
||||
- (NSData *)signData:(NSData *)data withKey:(NSData *)key {
|
||||
BIO* bio = BIO_new_mem_buf([key bytes], (int)[key length]);
|
||||
|
||||
EVP_PKEY* pkey;
|
||||
pkey = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL);
|
||||
|
||||
BIO_free(bio);
|
||||
|
||||
if (!pkey) {
|
||||
Log(LOG_E, @"Unable to parse private key in memory!");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
EVP_MD_CTX *mdctx = NULL;
|
||||
mdctx = EVP_MD_CTX_create();
|
||||
EVP_DigestSignInit(mdctx, NULL, EVP_sha256(), NULL, pkey);
|
||||
EVP_DigestSignUpdate(mdctx, [data bytes], [data length]);
|
||||
size_t slen;
|
||||
EVP_DigestSignFinal(mdctx, NULL, &slen);
|
||||
unsigned char* signature = malloc(slen);
|
||||
int result = EVP_DigestSignFinal(mdctx, signature, &slen);
|
||||
|
||||
EVP_PKEY_free(pkey);
|
||||
EVP_MD_CTX_destroy(mdctx);
|
||||
|
||||
if (result <= 0) {
|
||||
free(signature);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
NSData* signedData = [NSData dataWithBytes:signature length:slen];
|
||||
free(signature);
|
||||
|
||||
return signedData;
|
||||
}
|
||||
|
||||
+ (NSData*) readCryptoObject:(NSString*)item {
|
||||
#if TARGET_OS_TV
|
||||
return [[NSUserDefaults standardUserDefaults] dataForKey:item];
|
||||
#else
|
||||
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
|
||||
NSString *documentsDirectory = [paths objectAtIndex:0];
|
||||
NSString *file = [documentsDirectory stringByAppendingPathComponent:item];
|
||||
return [NSData dataWithContentsOfFile:file];
|
||||
#endif
|
||||
}
|
||||
|
||||
+ (void) writeCryptoObject:(NSString*)item data:(NSData*)data {
|
||||
#if TARGET_OS_TV
|
||||
[[NSUserDefaults standardUserDefaults] setObject:data forKey:item];
|
||||
#else
|
||||
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
|
||||
NSString *documentsDirectory = [paths objectAtIndex:0];
|
||||
NSString *file = [documentsDirectory stringByAppendingPathComponent:item];
|
||||
[data writeToFile:file atomically:NO];
|
||||
#endif
|
||||
}
|
||||
|
||||
+ (NSData*) readCertFromFile {
|
||||
if (cert == nil) {
|
||||
cert = [CryptoManager readCryptoObject:@"client.crt"];
|
||||
}
|
||||
return cert;
|
||||
}
|
||||
|
||||
+ (NSData*) readP12FromFile {
|
||||
if (p12 == nil) {
|
||||
p12 = [CryptoManager readCryptoObject:@"client.p12"];
|
||||
}
|
||||
return p12;
|
||||
}
|
||||
|
||||
+ (NSData*) readKeyFromFile {
|
||||
if (key == nil) {
|
||||
key = [CryptoManager readCryptoObject:@"client.key"];
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
+ (bool) keyPairExists {
|
||||
bool keyFileExists = [CryptoManager readCryptoObject:@"client.key"] != nil;
|
||||
bool p12FileExists = [CryptoManager readCryptoObject:@"client.p12"] != nil;
|
||||
bool certFileExists = [CryptoManager readCryptoObject:@"client.crt"] != nil;
|
||||
|
||||
return keyFileExists && p12FileExists && certFileExists;
|
||||
}
|
||||
|
||||
+ (NSData *)getSignatureFromCert:(NSData *)cert {
|
||||
BIO* bio = BIO_new_mem_buf([cert bytes], (int)[cert length]);
|
||||
X509* x509 = PEM_read_bio_X509(bio, NULL, NULL, NULL);
|
||||
BIO_free(bio);
|
||||
|
||||
if (!x509) {
|
||||
Log(LOG_E, @"Unable to parse certificate in memory!");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#if (OPENSSL_VERSION_NUMBER < 0x10002000L)
|
||||
ASN1_BIT_STRING *asnSignature = x509->signature;
|
||||
#elif (OPENSSL_VERSION_NUMBER < 0x10100000L)
|
||||
ASN1_BIT_STRING *asnSignature;
|
||||
X509_get0_signature(&asnSignature, NULL, x509);
|
||||
#else
|
||||
const ASN1_BIT_STRING *asnSignature;
|
||||
X509_get0_signature(&asnSignature, NULL, x509);
|
||||
#endif
|
||||
|
||||
NSData* sig = [NSData dataWithBytes:asnSignature->data length:asnSignature->length];
|
||||
|
||||
X509_free(x509);
|
||||
|
||||
return sig;
|
||||
}
|
||||
|
||||
+ (NSData*)getKeyFromCertKeyPair:(CertKeyPair*)certKeyPair {
|
||||
BIO* bio = BIO_new(BIO_s_mem());
|
||||
|
||||
PEM_write_bio_PrivateKey_traditional(bio, certKeyPair->pkey, NULL, NULL, 0, NULL, NULL);
|
||||
|
||||
BUF_MEM* mem;
|
||||
BIO_get_mem_ptr(bio, &mem);
|
||||
NSData* data = [NSData dataWithBytes:mem->data length:mem->length];
|
||||
BIO_free(bio);
|
||||
return data;
|
||||
}
|
||||
|
||||
+ (NSData*)getP12FromCertKeyPair:(CertKeyPair*)certKeyPair {
|
||||
BIO* bio = BIO_new(BIO_s_mem());
|
||||
|
||||
i2d_PKCS12_bio(bio, certKeyPair->p12);
|
||||
|
||||
BUF_MEM* mem;
|
||||
BIO_get_mem_ptr(bio, &mem);
|
||||
NSData* data = [NSData dataWithBytes:mem->data length:mem->length];
|
||||
BIO_free(bio);
|
||||
return data;
|
||||
}
|
||||
|
||||
+ (NSData*)getCertFromCertKeyPair:(CertKeyPair*)certKeyPair {
|
||||
BIO* bio = BIO_new(BIO_s_mem());
|
||||
|
||||
PEM_write_bio_X509(bio, certKeyPair->x509);
|
||||
|
||||
BUF_MEM* mem;
|
||||
BIO_get_mem_ptr(bio, &mem);
|
||||
NSData* data = [NSData dataWithBytes:mem->data length:mem->length];
|
||||
BIO_free(bio);
|
||||
return data;
|
||||
}
|
||||
|
||||
+ (void) generateKeyPairUsingSSL {
|
||||
static dispatch_once_t pred;
|
||||
dispatch_once(&pred, ^{
|
||||
if (![CryptoManager keyPairExists]) {
|
||||
Log(LOG_I, @"Generating Certificate... ");
|
||||
CertKeyPair certKeyPair = generateCertKeyPair();
|
||||
|
||||
NSData* certData = [CryptoManager getCertFromCertKeyPair:&certKeyPair];
|
||||
NSData* p12Data = [CryptoManager getP12FromCertKeyPair:&certKeyPair];
|
||||
NSData* keyData = [CryptoManager getKeyFromCertKeyPair:&certKeyPair];
|
||||
|
||||
freeCertKeyPair(certKeyPair);
|
||||
|
||||
[CryptoManager writeCryptoObject:@"client.crt" data:certData];
|
||||
[CryptoManager writeCryptoObject:@"client.p12" data:p12Data];
|
||||
[CryptoManager writeCryptoObject:@"client.key" data:keyData];
|
||||
|
||||
Log(LOG_I, @"Certificate created");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,13 +0,0 @@
|
||||
//
|
||||
// IdManager.h
|
||||
// Moonlight
|
||||
//
|
||||
// Created by Diego Waxemberg on 10/31/15.
|
||||
// Copyright © 2015 Moonlight Stream. All rights reserved.
|
||||
//
|
||||
|
||||
@interface IdManager : NSObject
|
||||
|
||||
+ (NSString*) getUniqueId;
|
||||
|
||||
@end
|
||||
@@ -1,32 +0,0 @@
|
||||
//
|
||||
// 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
|
||||
@@ -1,97 +0,0 @@
|
||||
|
||||
#include "mkcert.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/pem.h>
|
||||
#include <OpenSSL/provider.h>
|
||||
#include <OpenSSL/rsa.h>
|
||||
#include <openssl/x509.h>
|
||||
#include <OpenSSL/rand.h>
|
||||
|
||||
static const int NUM_BITS = 2048;
|
||||
static const int SERIAL = 0;
|
||||
static const int NUM_YEARS = 20;
|
||||
|
||||
void mkcert(X509 **x509p, EVP_PKEY **pkeyp, int bits, int serial, int years) {
|
||||
X509* cert = X509_new();
|
||||
|
||||
EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL);
|
||||
|
||||
EVP_PKEY_keygen_init(ctx);
|
||||
EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, bits);
|
||||
|
||||
// pk must be initialized on input
|
||||
EVP_PKEY* pk = NULL;
|
||||
EVP_PKEY_keygen(ctx, &pk);
|
||||
|
||||
EVP_PKEY_CTX_free(ctx);
|
||||
|
||||
X509_set_version(cert, 2);
|
||||
ASN1_INTEGER_set(X509_get_serialNumber(cert), serial);
|
||||
#if OPENSSL_VERSION_NUMBER < 0x10100000L
|
||||
X509_gmtime_adj(X509_get_notBefore(cert), 0);
|
||||
X509_gmtime_adj(X509_get_notAfter(cert), 60 * 60 * 24 * 365 * years);
|
||||
#else
|
||||
ASN1_TIME* before = ASN1_STRING_dup(X509_get0_notBefore(cert));
|
||||
ASN1_TIME* after = ASN1_STRING_dup(X509_get0_notAfter(cert));
|
||||
|
||||
X509_gmtime_adj(before, 0);
|
||||
X509_gmtime_adj(after, 60 * 60 * 24 * 365 * years);
|
||||
|
||||
X509_set1_notBefore(cert, before);
|
||||
X509_set1_notAfter(cert, after);
|
||||
|
||||
ASN1_STRING_free(before);
|
||||
ASN1_STRING_free(after);
|
||||
#endif
|
||||
|
||||
X509_set_pubkey(cert, pk);
|
||||
|
||||
X509_NAME* name = X509_get_subject_name(cert);
|
||||
X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC,
|
||||
(const unsigned char*)"NVIDIA GameStream Client",
|
||||
-1, -1, 0);
|
||||
X509_set_issuer_name(cert, name);
|
||||
|
||||
X509_sign(cert, pk, EVP_sha256());
|
||||
|
||||
*x509p = cert;
|
||||
*pkeyp = pk;
|
||||
}
|
||||
|
||||
struct CertKeyPair generateCertKeyPair(void) {
|
||||
X509 *x509 = NULL;
|
||||
EVP_PKEY *pkey = NULL;
|
||||
PKCS12 *p12 = NULL;
|
||||
|
||||
mkcert(&x509, &pkey, NUM_BITS, SERIAL, NUM_YEARS);
|
||||
|
||||
char* pass = "limelight";
|
||||
p12 = PKCS12_create(pass,
|
||||
"GameStream",
|
||||
pkey,
|
||||
x509,
|
||||
NULL,
|
||||
NID_pbe_WithSHA1And3_Key_TripleDES_CBC,
|
||||
-1, // disable certificate encryption
|
||||
2048,
|
||||
-1, // disable the automatic MAC
|
||||
0);
|
||||
// MAC it ourselves with SHA1 since iOS refuses to load anything else.
|
||||
PKCS12_set_mac(p12, pass, -1, NULL, 0, 1, EVP_sha1());
|
||||
|
||||
if (p12 == NULL) {
|
||||
printf("Error generating a valid PKCS12 certificate.\n");
|
||||
}
|
||||
|
||||
return (CertKeyPair){x509, pkey, p12};
|
||||
}
|
||||
|
||||
void freeCertKeyPair(struct CertKeyPair certKeyPair) {
|
||||
X509_free(certKeyPair.x509);
|
||||
EVP_PKEY_free(certKeyPair.pkey);
|
||||
PKCS12_free(certKeyPair.p12);
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
//
|
||||
// mkcert.h
|
||||
// Moonlight
|
||||
//
|
||||
// Created by Diego Waxemberg on 10/16/14.
|
||||
// Copyright (c) 2014 Moonlight Stream. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Limelight_mkcert_h
|
||||
#define Limelight_mkcert_h
|
||||
|
||||
#include <sys/_types/_time_t.h>
|
||||
#include <openssl/x509v3.h>
|
||||
#include <openssl/pkcs12.h>
|
||||
|
||||
typedef struct CertKeyPair {
|
||||
X509 *x509;
|
||||
EVP_PKEY *pkey;
|
||||
PKCS12 *p12;
|
||||
} CertKeyPair;
|
||||
|
||||
struct CertKeyPair generateCertKeyPair(void);
|
||||
void freeCertKeyPair(CertKeyPair);
|
||||
#endif
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
//
|
||||
// DataManager.h
|
||||
// Moonlight
|
||||
//
|
||||
// Created by Diego Waxemberg on 10/28/14.
|
||||
// Copyright (c) 2014 Moonlight Stream. All rights reserved.
|
||||
//
|
||||
// Modified by True砖家 since 2024.6.1
|
||||
// Copyright © 2024 True砖家 @ Bilibili. All rights reserved.
|
||||
//
|
||||
|
||||
#import "AppDelegate.h"
|
||||
#import "TemporaryHost.h"
|
||||
#import "TemporaryApp.h"
|
||||
#import "TemporarySettings.h"
|
||||
|
||||
@interface DataManager : NSObject
|
||||
|
||||
typedef NS_ENUM(NSUInteger, UINavigationBarHeight) {
|
||||
UINavigationBarHeightIPad = 50,
|
||||
UINavigationBarHeightIPhone = 44
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSInteger, TouchMode) {
|
||||
RelativeTouch,
|
||||
NativeTouch,
|
||||
AbsoluteTouch,
|
||||
NativeTouchOnly
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSInteger, TouchEventPriorityEnum) {
|
||||
AsyncNativeTouchOff,
|
||||
EqualPriority,
|
||||
TouchDownPriority,// deprecated by GUI
|
||||
TouchMovePriority// deprecated by GUI
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSInteger, SettingsMenuMode) {
|
||||
AllSettings,
|
||||
FavoriteSettings,
|
||||
RemoveSettingItem,
|
||||
};
|
||||
|
||||
- (void) saveSettingsWithBitrate:(NSInteger)bitrate
|
||||
framerate:(NSInteger)framerate
|
||||
height:(NSInteger)height
|
||||
width:(NSInteger)width
|
||||
audioConfig:(NSInteger)audioConfig
|
||||
onscreenControls:(NSInteger)onscreenControls
|
||||
keyboardToggleFingers:(NSInteger)keyboardToggleFingers
|
||||
oscLayoutToolFingers:(NSInteger)oscLayoutToolFingers
|
||||
slideToSettingsScreenEdge:(NSInteger)slideToSettingsScreenEdge
|
||||
slideToSettingsDistance:(CGFloat)slideToSettingsDistance
|
||||
pointerVelocityModeDivider:(CGFloat)pointerVelocityModeDivider
|
||||
touchPointerVelocityFactor:(CGFloat)touchPointerVelocityFactor
|
||||
mousePointerVelocityFactor:(CGFloat)mousePointerVelocityFactor
|
||||
touchMoveEventInterval:(NSInteger)touchMoveEventInterval
|
||||
reverseMouseWheelDirection:(BOOL)reverseMouseWheelDirection
|
||||
asyncNativeTouchPriority:(NSInteger)asyncNativeTouchPriority
|
||||
liftStreamViewForKeyboard:(BOOL)liftStreamViewForKeyboard
|
||||
showKeyboardToolbar:(BOOL)showKeyboardToolbar
|
||||
optimizeGames:(BOOL)optimizeGames
|
||||
multiController:(BOOL)multiController
|
||||
swapABXYButtons:(BOOL)swapABXYButtons
|
||||
audioOnPC:(BOOL)audioOnPC
|
||||
preferredCodec:(uint32_t)preferredCodec
|
||||
enableYUV444:(BOOL)enableYUV444
|
||||
useFramePacing:(BOOL)useFramePacing
|
||||
enableHdr:(BOOL)enableHdr
|
||||
btMouseSupport:(BOOL)btMouseSupport
|
||||
// absoluteTouchMode:(BOOL)absoluteTouchMode
|
||||
touchMode:(NSInteger)touchMode
|
||||
statsOverlayLevel:(NSInteger)statsOverlayLevel
|
||||
statsOverlayEnabled:(BOOL)statsOverlayEnabled
|
||||
unlockDisplayOrientation:(BOOL)unlockDisplayOrientation
|
||||
resolutionSelected:(NSInteger)resolutionSelected
|
||||
externalDisplayMode:(NSInteger)externalDisplayMode
|
||||
localMousePointerMode:(NSInteger)localMousePointerMode;
|
||||
|
||||
- (NSArray*) getHosts;
|
||||
- (void) updateHost:(TemporaryHost*)host;
|
||||
- (void) updateAppsForExistingHost:(TemporaryHost *)host;
|
||||
- (void) removeHost:(TemporaryHost*)host;
|
||||
- (void) removeApp:(TemporaryApp*)app;
|
||||
- (Settings*) retrieveSettings;
|
||||
- (void) saveData;
|
||||
- (TemporarySettings*) getSettings;
|
||||
|
||||
- (void) updateUniqueId:(NSString*)uniqueId;
|
||||
- (NSString*) getUniqueId;
|
||||
|
||||
@end
|
||||
@@ -1,285 +0,0 @@
|
||||
//
|
||||
// DataManager.m
|
||||
// Moonlight
|
||||
//
|
||||
// Created by Diego Waxemberg on 10/28/14.
|
||||
// Copyright (c) 2014 Moonlight Stream. All rights reserved.
|
||||
//
|
||||
// Modified by True砖家 since 2024.6.1
|
||||
// Copyright © 2024 True砖家 @ Bilibili. All rights reserved.
|
||||
//
|
||||
|
||||
#import "DataManager.h"
|
||||
#import "TemporaryApp.h"
|
||||
#import "TemporarySettings.h"
|
||||
|
||||
@implementation DataManager {
|
||||
NSManagedObjectContext *_managedObjectContext;
|
||||
AppDelegate *_appDelegate;
|
||||
}
|
||||
|
||||
- (id) init {
|
||||
self = [super init];
|
||||
|
||||
// HACK: Avoid calling [UIApplication delegate] off the UI thread to keep
|
||||
// Main Thread Checker happy.
|
||||
if ([NSThread isMainThread]) {
|
||||
_appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
|
||||
}
|
||||
else {
|
||||
dispatch_sync(dispatch_get_main_queue(), ^{
|
||||
self->_appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
|
||||
});
|
||||
}
|
||||
|
||||
_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
|
||||
[_managedObjectContext setParentContext:[_appDelegate managedObjectContext]];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void) updateUniqueId:(NSString*)uniqueId {
|
||||
[_managedObjectContext performBlockAndWait:^{
|
||||
[self retrieveSettings].uniqueId = uniqueId;
|
||||
[self saveData];
|
||||
}];
|
||||
}
|
||||
|
||||
- (NSString*) getUniqueId {
|
||||
__block NSString *uid;
|
||||
|
||||
[_managedObjectContext performBlockAndWait:^{
|
||||
uid = [self retrieveSettings].uniqueId;
|
||||
}];
|
||||
|
||||
return uid;
|
||||
}
|
||||
|
||||
- (void) saveSettingsWithBitrate:(NSInteger)bitrate
|
||||
framerate:(NSInteger)framerate
|
||||
height:(NSInteger)height
|
||||
width:(NSInteger)width
|
||||
audioConfig:(NSInteger)audioConfig
|
||||
onscreenControls:(NSInteger)onscreenControls
|
||||
keyboardToggleFingers:(NSInteger)keyboardToggleFingers
|
||||
oscLayoutToolFingers:(NSInteger)oscLayoutToolFingers
|
||||
slideToSettingsScreenEdge:(NSInteger)slideToSettingsScreenEdge
|
||||
slideToSettingsDistance:(CGFloat)slideToSettingsDistance
|
||||
pointerVelocityModeDivider:(CGFloat)pointerVelocityModeDivider
|
||||
touchPointerVelocityFactor:(CGFloat)touchPointerVelocityFactor
|
||||
mousePointerVelocityFactor:(CGFloat)mousePointerVelocityFactor
|
||||
touchMoveEventInterval:(NSInteger)touchMoveEventInterval
|
||||
reverseMouseWheelDirection:(BOOL)reverseMouseWheelDirection
|
||||
asyncNativeTouchPriority:(NSInteger)asyncNativeTouchPriority
|
||||
liftStreamViewForKeyboard:(BOOL)liftStreamViewForKeyboard
|
||||
showKeyboardToolbar:(BOOL)showKeyboardToolbar
|
||||
optimizeGames:(BOOL)optimizeGames
|
||||
multiController:(BOOL)multiController
|
||||
swapABXYButtons:(BOOL)swapABXYButtons
|
||||
audioOnPC:(BOOL)audioOnPC
|
||||
preferredCodec:(uint32_t)preferredCodec
|
||||
enableYUV444:(BOOL)enableYUV444
|
||||
useFramePacing:(BOOL)useFramePacing
|
||||
enableHdr:(BOOL)enableHdr
|
||||
btMouseSupport:(BOOL)btMouseSupport
|
||||
// absoluteTouchMode:(BOOL)absoluteTouchMode
|
||||
touchMode:(NSInteger)touchMode
|
||||
statsOverlayLevel:(NSInteger)statsOverlayLevel
|
||||
statsOverlayEnabled:(BOOL)statsOverlayEnabled
|
||||
unlockDisplayOrientation:(BOOL)unlockDisplayOrientation
|
||||
resolutionSelected:(NSInteger)resolutionSelected
|
||||
externalDisplayMode:(NSInteger)externalDisplayMode
|
||||
localMousePointerMode:(NSInteger)localMousePointerMode{
|
||||
|
||||
[_managedObjectContext performBlockAndWait:^{
|
||||
Settings* settingsToSave = [self retrieveSettings];
|
||||
settingsToSave.framerate = [NSNumber numberWithInteger:framerate];
|
||||
settingsToSave.bitrate = [NSNumber numberWithInteger:bitrate];
|
||||
settingsToSave.height = [NSNumber numberWithInteger:height];
|
||||
settingsToSave.width = [NSNumber numberWithInteger:width];
|
||||
settingsToSave.audioConfig = [NSNumber numberWithInteger:audioConfig];
|
||||
settingsToSave.onscreenControls = [NSNumber numberWithInteger:onscreenControls];
|
||||
settingsToSave.keyboardToggleFingers = [NSNumber numberWithInteger:(uint16_t)keyboardToggleFingers];
|
||||
settingsToSave.oscLayoutToolFingers = [NSNumber numberWithInteger:(uint16_t)oscLayoutToolFingers];
|
||||
settingsToSave.slideToSettingsScreenEdge = [NSNumber numberWithInteger:(uint32_t)slideToSettingsScreenEdge];
|
||||
settingsToSave.slideToSettingsDistance = [NSNumber numberWithFloat:slideToSettingsDistance];
|
||||
settingsToSave.pointerVelocityModeDivider = [NSNumber numberWithFloat:pointerVelocityModeDivider];
|
||||
settingsToSave.touchPointerVelocityFactor = [NSNumber numberWithFloat:touchPointerVelocityFactor];
|
||||
settingsToSave.mousePointerVelocityFactor = [NSNumber numberWithFloat:mousePointerVelocityFactor];
|
||||
settingsToSave.touchMoveEventInterval = [NSNumber numberWithInteger:touchMoveEventInterval];
|
||||
settingsToSave.reverseMouseWheelDirection = reverseMouseWheelDirection;
|
||||
settingsToSave.asyncNativeTouchPriority = [NSNumber numberWithInteger:asyncNativeTouchPriority];
|
||||
settingsToSave.liftStreamViewForKeyboard = liftStreamViewForKeyboard;
|
||||
settingsToSave.showKeyboardToolbar = showKeyboardToolbar;
|
||||
settingsToSave.optimizeGames = optimizeGames;
|
||||
settingsToSave.multiController = multiController;
|
||||
settingsToSave.swapABXYButtons = swapABXYButtons;
|
||||
settingsToSave.playAudioOnPC = audioOnPC;
|
||||
settingsToSave.preferredCodec = preferredCodec;
|
||||
settingsToSave.enableYUV444 = enableYUV444;
|
||||
settingsToSave.useFramePacing = useFramePacing;
|
||||
settingsToSave.enableHdr = enableHdr;
|
||||
settingsToSave.btMouseSupport = btMouseSupport;
|
||||
// settingsToSave.absoluteTouchMode = absoluteTouchMode;
|
||||
settingsToSave.touchMode = [NSNumber numberWithInteger:(uint16_t)touchMode];
|
||||
settingsToSave.statsOverlayEnabled = statsOverlayEnabled;
|
||||
settingsToSave.statsOverlayLevel = [NSNumber numberWithInteger:(uint16_t)statsOverlayLevel];
|
||||
settingsToSave.unlockDisplayOrientation = unlockDisplayOrientation;
|
||||
settingsToSave.resolutionSelected = [NSNumber numberWithInteger:resolutionSelected];
|
||||
settingsToSave.externalDisplayMode = [NSNumber numberWithInteger:externalDisplayMode];
|
||||
settingsToSave.localMousePointerMode = [NSNumber numberWithInteger:localMousePointerMode];
|
||||
[self saveData];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void) updateHost:(TemporaryHost *)host {
|
||||
[_managedObjectContext performBlockAndWait:^{
|
||||
// Add a new persistent managed object if one doesn't exist
|
||||
Host* parent = [self getHostForTemporaryHost:host withHostRecords:[self fetchRecords:@"Host"]];
|
||||
if (parent == nil) {
|
||||
NSEntityDescription* entity = [NSEntityDescription entityForName:@"Host" inManagedObjectContext:self->_managedObjectContext];
|
||||
parent = [[Host alloc] initWithEntity:entity insertIntoManagedObjectContext:self->_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 withHostRecords:[self fetchRecords:@"Host"]];
|
||||
if (parent == nil) {
|
||||
// The host must exist to be updated
|
||||
return;
|
||||
}
|
||||
|
||||
NSMutableSet *applist = [[NSMutableSet alloc] init];
|
||||
NSArray *appRecords = [self fetchRecords:@"App"];
|
||||
for (TemporaryApp* app in host.appList) {
|
||||
// Add a new persistent managed object if one doesn't exist
|
||||
App* parentApp = [self getAppForTemporaryApp:app withAppRecords:appRecords];
|
||||
if (parentApp == nil) {
|
||||
NSEntityDescription* entity = [NSEntityDescription entityForName:@"App" inManagedObjectContext:self->_managedObjectContext];
|
||||
parentApp = [[App alloc] initWithEntity:entity insertIntoManagedObjectContext:self->_managedObjectContext];
|
||||
}
|
||||
|
||||
[app propagateChangesToParent:parentApp withHost:parent];
|
||||
|
||||
[applist addObject:parentApp];
|
||||
}
|
||||
|
||||
parent.appList = applist;
|
||||
|
||||
[self saveData];
|
||||
}];
|
||||
}
|
||||
|
||||
- (TemporarySettings*) getSettings {
|
||||
__block TemporarySettings *tempSettings;
|
||||
|
||||
[_managedObjectContext performBlockAndWait:^{
|
||||
tempSettings = [[TemporarySettings alloc] initFromSettings:[self retrieveSettings]];
|
||||
}];
|
||||
|
||||
return tempSettings;
|
||||
}
|
||||
|
||||
- (Settings*) retrieveSettings {
|
||||
NSArray* fetchedRecords = [self fetchRecords:@"Settings"];
|
||||
if (fetchedRecords.count == 0) {
|
||||
// create a new settings object with the default values
|
||||
NSEntityDescription* entity = [NSEntityDescription entityForName:@"Settings" inManagedObjectContext:_managedObjectContext];
|
||||
Settings* settings = [[Settings alloc] initWithEntity:entity insertIntoManagedObjectContext:_managedObjectContext];
|
||||
|
||||
return settings;
|
||||
} else {
|
||||
// we should only ever have 1 settings object stored
|
||||
return [fetchedRecords objectAtIndex:0];
|
||||
}
|
||||
}
|
||||
|
||||
- (void) removeApp:(TemporaryApp*)app {
|
||||
[_managedObjectContext performBlockAndWait:^{
|
||||
App* managedApp = [self getAppForTemporaryApp:app withAppRecords:[self fetchRecords:@"App"]];
|
||||
if (managedApp != nil) {
|
||||
[self->_managedObjectContext deleteObject:managedApp];
|
||||
[self saveData];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void) removeHost:(TemporaryHost*)host {
|
||||
[_managedObjectContext performBlockAndWait:^{
|
||||
Host* managedHost = [self getHostForTemporaryHost:host withHostRecords:[self fetchRecords:@"Host"]];
|
||||
if (managedHost != nil) {
|
||||
[self->_managedObjectContext deleteObject:managedHost];
|
||||
[self saveData];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void) saveData {
|
||||
NSError* error;
|
||||
if ([_managedObjectContext hasChanges] && ![_managedObjectContext save:&error]) {
|
||||
Log(LOG_E, @"Unable to save hosts to database: %@", error);
|
||||
}
|
||||
|
||||
[_appDelegate saveContext];
|
||||
}
|
||||
|
||||
- (NSArray*) getHosts {
|
||||
__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;
|
||||
}
|
||||
|
||||
// Only call from within performBlockAndWait!!!
|
||||
- (Host*) getHostForTemporaryHost:(TemporaryHost*)tempHost withHostRecords:(NSArray*)hosts {
|
||||
for (Host* host in hosts) {
|
||||
if ([tempHost.uuid isEqualToString:host.uuid]) {
|
||||
return host;
|
||||
}
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
// Only call from within performBlockAndWait!!!
|
||||
- (App*) getAppForTemporaryApp:(TemporaryApp*)tempApp withAppRecords:(NSArray*)apps {
|
||||
for (App* app in apps) {
|
||||
if ([app.id isEqualToString:tempApp.id] &&
|
||||
[app.host.uuid isEqualToString:tempApp.host.uuid]) {
|
||||
return app;
|
||||
}
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (NSArray*) fetchRecords:(NSString*)entityName {
|
||||
NSArray* fetchedRecords;
|
||||
|
||||
NSFetchRequest* fetchRequest = [[NSFetchRequest alloc] init];
|
||||
NSEntityDescription* entity = [NSEntityDescription entityForName:entityName inManagedObjectContext:_managedObjectContext];
|
||||
[fetchRequest setEntity:entity];
|
||||
|
||||
NSError* error;
|
||||
fetchedRecords = [_managedObjectContext executeFetchRequest:fetchRequest error:&error];
|
||||
//TODO: handle errors
|
||||
|
||||
return fetchedRecords;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
//
|
||||
// TemporaryApp.h
|
||||
// Moonlight
|
||||
//
|
||||
// Created by Cameron Gutman on 9/30/15.
|
||||
// Copyright © 2015 Moonlight Stream. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TemporaryHost.h"
|
||||
#import "App+CoreDataClass.h"
|
||||
|
||||
@interface TemporaryApp : NSObject
|
||||
|
||||
@property (nullable, nonatomic, retain) NSString *id;
|
||||
@property (nullable, nonatomic, retain) NSString *name;
|
||||
@property (nullable, nonatomic, retain) NSString *installPath;
|
||||
@property (nonatomic) BOOL hdrSupported;
|
||||
@property (nonatomic) BOOL hidden;
|
||||
@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
|
||||
@@ -1,54 +0,0 @@
|
||||
//
|
||||
// TemporaryApp.m
|
||||
// Moonlight
|
||||
//
|
||||
// Created by Cameron Gutman on 9/30/15.
|
||||
// Copyright © 2015 Moonlight Stream. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TemporaryApp.h"
|
||||
|
||||
@implementation TemporaryApp
|
||||
|
||||
- (id) initFromApp:(App*)app withTempHost:(TemporaryHost*)tempHost {
|
||||
self = [self init];
|
||||
|
||||
self.id = app.id;
|
||||
self.name = app.name;
|
||||
self.hdrSupported = app.hdrSupported;
|
||||
self.hidden = app.hidden;
|
||||
self.host = tempHost;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void) propagateChangesToParent:(App*)parent withHost:(Host*)host {
|
||||
parent.id = self.id;
|
||||
parent.name = self.name;
|
||||
parent.hdrSupported = self.hdrSupported;
|
||||
parent.hidden = self.hidden;
|
||||
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:[self class]]) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
return [self.host.uuid isEqualToString:((App*)object).host.uuid] &&
|
||||
[self.id isEqualToString:((App*)object).id];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,43 +0,0 @@
|
||||
//
|
||||
// TemporaryHost.h
|
||||
// Moonlight
|
||||
//
|
||||
// Created by Cameron Gutman on 12/1/15.
|
||||
// Copyright © 2015 Moonlight Stream. All rights reserved.
|
||||
//
|
||||
|
||||
#import "Utils.h"
|
||||
#import "Host+CoreDataClass.h"
|
||||
|
||||
@interface TemporaryHost : NSObject
|
||||
|
||||
@property (atomic) State state;
|
||||
@property (atomic) PairState pairState;
|
||||
@property (atomic, nullable, retain) NSString * activeAddress;
|
||||
@property (atomic, nullable, retain) NSString * currentGame;
|
||||
@property (atomic) unsigned short httpsPort;
|
||||
@property (atomic) BOOL isNvidiaServerSoftware;
|
||||
|
||||
@property (atomic, nullable, retain) NSData *serverCert;
|
||||
@property (atomic, nullable, retain) NSString *address;
|
||||
@property (atomic, nullable, retain) NSString *externalAddress;
|
||||
@property (atomic, nullable, retain) NSString *localAddress;
|
||||
@property (atomic, nullable, retain) NSString *ipv6Address;
|
||||
@property (atomic, nullable, retain) NSString *mac;
|
||||
@property (atomic) int serverCodecModeSupport;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@property (atomic, retain) NSString *name;
|
||||
@property (atomic, retain) NSString *uuid;
|
||||
@property (atomic, retain) NSSet *appList;
|
||||
|
||||
- (id) initFromHost:(Host*)host;
|
||||
|
||||
- (NSComparisonResult)compareName:(TemporaryHost *)other;
|
||||
|
||||
- (void) propagateChangesToParent:(Host*)host;
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@end
|
||||
@@ -1,108 +0,0 @@
|
||||
//
|
||||
// TemporaryHost.m
|
||||
// Moonlight
|
||||
//
|
||||
// Created by Cameron Gutman on 12/1/15.
|
||||
// Copyright © 2015 Moonlight Stream. All rights reserved.
|
||||
//
|
||||
// Modified by True砖家 since 2024.8.3
|
||||
// Copyright © 2024 True砖家 @ Bilibili. All rights reserved.
|
||||
//
|
||||
|
||||
#import "DataManager.h"
|
||||
#import "TemporaryHost.h"
|
||||
#import "TemporaryApp.h"
|
||||
|
||||
@implementation TemporaryHost
|
||||
|
||||
- (id) init {
|
||||
self = [super init];
|
||||
self.appList = [[NSMutableSet alloc] init];
|
||||
self.currentGame = @"0";
|
||||
self.state = StateUnknown;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id) initFromHost:(Host*)host {
|
||||
self = [self init];
|
||||
|
||||
self.address = host.address;
|
||||
self.externalAddress = host.externalAddress;
|
||||
self.localAddress = host.localAddress;
|
||||
self.ipv6Address = host.ipv6Address;
|
||||
if(!(host.mac == nil || [host.mac isEqualToString:@"00:00:00:00:00:00"])) self.mac = host.mac; // try to fix invalid mac happens in some cases
|
||||
self.name = host.name;
|
||||
self.uuid = host.uuid;
|
||||
self.serverCodecModeSupport = host.serverCodecModeSupport;
|
||||
self.serverCert = host.serverCert;
|
||||
|
||||
// Older clients stored a non-URL-escaped IPv6 string. Try to detect that and fix it up.
|
||||
if (![self.ipv6Address containsString:@"["]) {
|
||||
self.ipv6Address = [Utils addressAndPortToAddressPortString:self.ipv6Address port:47989];
|
||||
}
|
||||
|
||||
// Ensure we don't use a stale cached pair state if we haven't pinned the cert yet
|
||||
self.pairState = host.serverCert ? [host.pairState intValue] : PairStateUnpaired;
|
||||
|
||||
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 {
|
||||
// Avoid overwriting existing data with nil if
|
||||
// we don't have everything populated in the temporary
|
||||
// host.
|
||||
if (self.address != nil) {
|
||||
parentHost.address = self.address;
|
||||
}
|
||||
if (self.externalAddress != nil) {
|
||||
parentHost.externalAddress = self.externalAddress;
|
||||
}
|
||||
if (self.localAddress != nil) {
|
||||
parentHost.localAddress = self.localAddress;
|
||||
}
|
||||
if (self.ipv6Address != nil) {
|
||||
parentHost.ipv6Address = self.ipv6Address;
|
||||
}
|
||||
// try to fix invalid mac happens in some cases
|
||||
// 添加主机crash问题重点关注
|
||||
if (!(self.mac == nil || [self.mac isEqualToString:@"00:00:00:00:00:00"])) parentHost.mac = self.mac;
|
||||
if (self.serverCert != nil) {
|
||||
parentHost.serverCert = self.serverCert;
|
||||
}
|
||||
parentHost.name = self.name;
|
||||
parentHost.uuid = self.uuid;
|
||||
parentHost.serverCodecModeSupport = self.serverCodecModeSupport;
|
||||
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:[self class]]) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
return [self.uuid isEqualToString:((Host*)object).uuid];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,66 +0,0 @@
|
||||
//
|
||||
// TemporarySettings.h
|
||||
// Moonlight
|
||||
//
|
||||
// Created by Cameron Gutman on 12/1/15.
|
||||
// Copyright © 2015 Moonlight Stream. All rights reserved.
|
||||
//
|
||||
// Modified by True砖家 since 2024.6.1
|
||||
// Copyright © 2024 True砖家 @ Bilibili. All rights reserved.
|
||||
//
|
||||
|
||||
#import "Settings+CoreDataClass.h"
|
||||
#define OSC_TOOL_FINGERS 4
|
||||
|
||||
|
||||
@interface TemporarySettings : NSObject
|
||||
|
||||
@property (nonatomic, retain) Settings * parent;
|
||||
|
||||
@property (nonatomic, retain) NSNumber * settingsMenuMode;
|
||||
@property (nonatomic, retain) NSNumber * settingsMenuWidth;
|
||||
@property (nonatomic, retain) NSNumber * bitrate;
|
||||
@property (nonatomic, retain) NSNumber * framerate;
|
||||
@property (nonatomic, retain) NSNumber * height;
|
||||
@property (nonatomic, retain) NSNumber * width;
|
||||
@property (nonatomic, retain) NSNumber * audioConfig;
|
||||
@property (nonatomic, retain) NSNumber * onscreenControls;
|
||||
@property (nonatomic, retain) NSNumber * keyboardToggleFingers;
|
||||
@property (nonatomic, retain) NSNumber * oscLayoutToolFingers;
|
||||
@property (nonatomic, retain) NSNumber * slideToSettingsScreenEdge;
|
||||
@property (nonatomic, retain) NSNumber * slideToSettingsDistance;
|
||||
@property (nonatomic, retain) NSNumber * touchMoveEventInterval;
|
||||
@property (nonatomic, retain) NSNumber * touchPointerVelocityFactor;
|
||||
@property (nonatomic, retain) NSNumber * mousePointerVelocityFactor;
|
||||
@property (nonatomic, retain) NSNumber * pointerVelocityModeDivider;
|
||||
@property (nonatomic, retain) NSString * uniqueId;
|
||||
@property (nonatomic, retain) NSNumber * resolutionSelected;
|
||||
@property (nonatomic, retain) NSNumber * externalDisplayMode;
|
||||
@property (nonatomic, retain) NSNumber * localMousePointerMode;
|
||||
@property (nonatomic) enum {
|
||||
CODEC_PREF_AUTO,
|
||||
CODEC_PREF_H264,
|
||||
CODEC_PREF_HEVC,
|
||||
CODEC_PREF_AV1,
|
||||
} preferredCodec;
|
||||
@property (nonatomic) BOOL enableYUV444;
|
||||
@property (nonatomic) BOOL reverseMouseWheelDirection;
|
||||
@property (nonatomic, retain) NSNumber * asyncNativeTouchPriority;
|
||||
@property (nonatomic) BOOL useFramePacing;
|
||||
@property (nonatomic) BOOL multiController;
|
||||
@property (nonatomic) BOOL swapABXYButtons;
|
||||
@property (nonatomic) BOOL playAudioOnPC;
|
||||
@property (nonatomic) BOOL optimizeGames;
|
||||
@property (nonatomic) BOOL enableHdr;
|
||||
@property (nonatomic) BOOL btMouseSupport;
|
||||
// @property (nonatomic) BOOL absoluteTouchMode;
|
||||
@property (nonatomic, retain) NSNumber * touchMode;
|
||||
@property (nonatomic, retain) NSNumber * statsOverlayLevel;
|
||||
@property (nonatomic) BOOL statsOverlayEnabled;
|
||||
@property (nonatomic) BOOL liftStreamViewForKeyboard;
|
||||
@property (nonatomic) BOOL showKeyboardToolbar;
|
||||
@property (nonatomic) BOOL unlockDisplayOrientation;
|
||||
|
||||
- (id) initFromSettings:(Settings*)settings;
|
||||
|
||||
@end
|
||||
@@ -1,119 +0,0 @@
|
||||
//
|
||||
// TemporarySettings.m
|
||||
// Moonlight
|
||||
//
|
||||
// Created by Cameron Gutman on 12/1/15.
|
||||
// Copyright © 2015 Moonlight Stream. All rights reserved.
|
||||
//
|
||||
// Modified by True砖家 since 2024.6.1
|
||||
// Copyright © 2024 True砖家 @ Bilibili. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TemporarySettings.h"
|
||||
#import "OnScreenControls.h"
|
||||
|
||||
@implementation TemporarySettings
|
||||
|
||||
- (id) initFromSettings:(Settings*)settings {
|
||||
self = [self init];
|
||||
|
||||
self.parent = settings;
|
||||
|
||||
#if TARGET_OS_TV
|
||||
// Apply default values from our Root.plist
|
||||
NSString* settingsBundle = [[NSBundle mainBundle] pathForResource:@"Settings" ofType:@"bundle"];
|
||||
NSDictionary* settingsData = [NSDictionary dictionaryWithContentsOfFile:[settingsBundle stringByAppendingPathComponent:@"Root.plist"]];
|
||||
NSArray* preferences = [settingsData objectForKey:@"PreferenceSpecifiers"];
|
||||
NSMutableDictionary* defaultsToRegister = [[NSMutableDictionary alloc] initWithCapacity:[preferences count]];
|
||||
for (NSDictionary* prefSpecification in preferences) {
|
||||
NSString* key = [prefSpecification objectForKey:@"Key"];
|
||||
if (key != nil) {
|
||||
[defaultsToRegister setObject:[prefSpecification objectForKey:@"DefaultValue"] forKey:key];
|
||||
}
|
||||
}
|
||||
[[NSUserDefaults standardUserDefaults] registerDefaults:defaultsToRegister];
|
||||
|
||||
self.bitrate = [NSNumber numberWithInteger:[[NSUserDefaults standardUserDefaults] integerForKey:@"bitrate"]];
|
||||
assert([self.bitrate intValue] != 0);
|
||||
self.framerate = [NSNumber numberWithInteger:[[NSUserDefaults standardUserDefaults] integerForKey:@"framerate"]];
|
||||
assert([self.framerate intValue] != 0);
|
||||
self.audioConfig = [NSNumber numberWithInteger:[[NSUserDefaults standardUserDefaults] integerForKey:@"audioConfig"]];
|
||||
assert([self.audioConfig intValue] != 0);
|
||||
self.preferredCodec = (typeof(self.preferredCodec))[[NSUserDefaults standardUserDefaults] integerForKey:@"preferredCodec"];
|
||||
self.enableYUV444 = [[NSUserDefaults standardUserDefaults] boolForKey:@"enableYUV444"];
|
||||
self.useFramePacing = [[NSUserDefaults standardUserDefaults] integerForKey:@"useFramePacing"] != 0;
|
||||
self.playAudioOnPC = [[NSUserDefaults standardUserDefaults] boolForKey:@"audioOnPC"];
|
||||
self.enableHdr = [[NSUserDefaults standardUserDefaults] boolForKey:@"enableHdr"];
|
||||
self.optimizeGames = [[NSUserDefaults standardUserDefaults] boolForKey:@"optimizeGames"];
|
||||
self.multiController = [[NSUserDefaults standardUserDefaults] boolForKey:@"multipleControllers"];
|
||||
self.swapABXYButtons = [[NSUserDefaults standardUserDefaults] boolForKey:@"swapABXYButtons"];
|
||||
self.btMouseSupport = [[NSUserDefaults standardUserDefaults] boolForKey:@"btMouseSupport"];
|
||||
self.statsOverlay = [[NSUserDefaults standardUserDefaults] boolForKey:@"statsOverlay"];
|
||||
|
||||
NSInteger _screenSize = [[NSUserDefaults standardUserDefaults] integerForKey:@"streamResolution"];
|
||||
switch (_screenSize) {
|
||||
case 0:
|
||||
self.height = [NSNumber numberWithInteger:720];
|
||||
self.width = [NSNumber numberWithInteger:1280];
|
||||
break;
|
||||
case 1:
|
||||
self.height = [NSNumber numberWithInteger:1080];
|
||||
self.width = [NSNumber numberWithInteger:1920];
|
||||
break;
|
||||
case 2:
|
||||
self.height = [NSNumber numberWithInteger:2160];
|
||||
self.width = [NSNumber numberWithInteger:3840];
|
||||
break;
|
||||
case 3:
|
||||
self.height = [NSNumber numberWithInteger:1440];
|
||||
self.width = [NSNumber numberWithInteger:2560];
|
||||
break;
|
||||
default:
|
||||
abort();
|
||||
}
|
||||
self.onscreenControls = [NSNumber numberWithInteger:OnScreenControlsLevelOff];
|
||||
#else
|
||||
self.settingsMenuMode = settings.settingsMenuMode;
|
||||
self.settingsMenuWidth = settings.settingsMenuWidth;
|
||||
self.bitrate = settings.bitrate;
|
||||
self.framerate = settings.framerate;
|
||||
self.height = settings.height;
|
||||
self.width = settings.width;
|
||||
self.audioConfig = settings.audioConfig;
|
||||
self.preferredCodec = settings.preferredCodec;
|
||||
self.enableYUV444 = settings.enableYUV444;
|
||||
self.useFramePacing = settings.useFramePacing;
|
||||
self.playAudioOnPC = settings.playAudioOnPC;
|
||||
self.enableHdr = settings.enableHdr;
|
||||
self.optimizeGames = settings.optimizeGames;
|
||||
self.multiController = settings.multiController;
|
||||
self.swapABXYButtons = settings.swapABXYButtons;
|
||||
self.onscreenControls = settings.onscreenControls;
|
||||
self.reverseMouseWheelDirection = settings.reverseMouseWheelDirection;
|
||||
self.asyncNativeTouchPriority = settings.asyncNativeTouchPriority;
|
||||
self.btMouseSupport = settings.btMouseSupport;
|
||||
// self.absoluteTouchMode = settings.absoluteTouchMode;
|
||||
self.touchMode = settings.touchMode;
|
||||
self.statsOverlayLevel = settings.statsOverlayLevel;
|
||||
self.statsOverlayEnabled = settings.statsOverlayEnabled;
|
||||
self.keyboardToggleFingers = settings.keyboardToggleFingers;
|
||||
self.oscLayoutToolFingers = settings.oscLayoutToolFingers;
|
||||
self.slideToSettingsScreenEdge = settings.slideToSettingsScreenEdge;
|
||||
self.slideToSettingsDistance = settings.slideToSettingsDistance;
|
||||
self.liftStreamViewForKeyboard = settings.liftStreamViewForKeyboard;
|
||||
self.showKeyboardToolbar = settings.showKeyboardToolbar;
|
||||
self.touchMoveEventInterval = settings.touchMoveEventInterval;
|
||||
self.touchPointerVelocityFactor = settings.touchPointerVelocityFactor;
|
||||
self.mousePointerVelocityFactor = settings.mousePointerVelocityFactor;
|
||||
self.pointerVelocityModeDivider = settings.pointerVelocityModeDivider;
|
||||
self.unlockDisplayOrientation = settings.unlockDisplayOrientation;
|
||||
self.resolutionSelected = settings.resolutionSelected;
|
||||
self.externalDisplayMode = settings.externalDisplayMode;
|
||||
self.localMousePointerMode = settings.localMousePointerMode;
|
||||
#endif
|
||||
self.uniqueId = settings.uniqueId;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,14 +0,0 @@
|
||||
//
|
||||
// FixedTintImageView.h
|
||||
// VoidLink
|
||||
//
|
||||
// Created by True砖家 on 2025.6.1.
|
||||
// Copyright © 2025 True砖家 @ Bilibili. All rights reserved.
|
||||
//
|
||||
// MenuSectionView.m
|
||||
|
||||
@interface FixedTintImageView : UIImageView
|
||||
@end
|
||||
|
||||
@interface FixedTintButton : UIButton
|
||||
@end
|
||||
@@ -1,63 +0,0 @@
|
||||
//
|
||||
// FixedTintImageView.m
|
||||
// VoidLink
|
||||
//
|
||||
// Created by True砖家 on 2025/6/1.
|
||||
// Copyright © 2025 True砖家 @ Bilibili. All rights reserved.
|
||||
//
|
||||
|
||||
#import "FixedTintImageView.h"
|
||||
|
||||
|
||||
@interface FixedTintButton()
|
||||
@property (nonatomic, strong) UIColor *fixedTintColor;
|
||||
@end
|
||||
|
||||
@implementation FixedTintButton
|
||||
|
||||
- (void)tintColorDidChange {
|
||||
[super tintColorDidChange];
|
||||
// 强制 tint 保持固定颜色
|
||||
if (self.fixedTintColor) {
|
||||
[self setImage:[self.imageView.image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate] forState:UIControlStateNormal];
|
||||
self.tintColor = self.fixedTintColor;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setTintColor:(UIColor *)tintColor {
|
||||
_fixedTintColor = tintColor;
|
||||
[super setTintColor:tintColor];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
|
||||
@interface FixedTintImageView ()
|
||||
@property (nonatomic, strong) UIColor *fixedTintColor;
|
||||
@end
|
||||
|
||||
@implementation FixedTintImageView
|
||||
|
||||
- (instancetype)initWithImage:(UIImage *)image {
|
||||
self = [super initWithImage:[image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]];
|
||||
if (self) {
|
||||
// _fixedTintColor = [UIColor systemBlueColor]; // 默认颜色
|
||||
if(_fixedTintColor != nil) [super setTintColor:_fixedTintColor];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setTintColor:(UIColor *)tintColor {
|
||||
if(tintColor != nil){
|
||||
_fixedTintColor = tintColor;
|
||||
[super setTintColor:tintColor];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)tintColorDidChange {
|
||||
// 保持你固定的颜色,忽略系统的暗淡变更
|
||||
if(_fixedTintColor != nil) [super setTintColor:_fixedTintColor];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,32 +0,0 @@
|
||||
//
|
||||
// HostCardView.h
|
||||
// VoidLink
|
||||
//
|
||||
// Created by True砖家 on 5/18/25.
|
||||
// Copyright © 2025 True砖家 @ Bilibili. All rights reserved.
|
||||
//
|
||||
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "TemporaryHost.h"
|
||||
|
||||
@protocol HostCardActionDelegate <NSObject>
|
||||
- (void)appButtonTappedForHost:(TemporaryHost *)host;
|
||||
- (void)launchButtonTappedForHost:(TemporaryHost *)host;
|
||||
- (void)wakeupButtonTappedForHost:(TemporaryHost *)host;
|
||||
- (void)pairButtonTappedForHost:(TemporaryHost *)host;
|
||||
- (void)hostCardLongPressed:(TemporaryHost *)host view:(UIView *)view;
|
||||
@end
|
||||
|
||||
|
||||
@interface HostCardView : UIView
|
||||
@property (nonatomic, assign) CGFloat sizeFactor;
|
||||
@property (nonatomic, readonly) CGSize size;
|
||||
@property (nonatomic, weak) id<HostCardActionDelegate> delegate; // Delegate property
|
||||
|
||||
- (void)resizeBySizeFactor:(CGFloat)factor;
|
||||
- (id) initWithHost:(TemporaryHost*)host;
|
||||
- (id) initWithHost:(TemporaryHost*)host andSizeFactor:(CGFloat)sizeFactor;
|
||||
|
||||
@end
|
||||
|
||||
@@ -1,619 +0,0 @@
|
||||
//
|
||||
// HostCardView.m
|
||||
// VoidLink
|
||||
//
|
||||
// Created by True砖家 on 5/18/25.
|
||||
// Copyright © 2025 True砖家 @ Bilibili. All rights reserved.
|
||||
//
|
||||
|
||||
#import "HostCardView.h"
|
||||
#import "LocalizationHelper.h"
|
||||
#import "ThemeManager.h"
|
||||
#import "FixedTintImageView.h"
|
||||
|
||||
|
||||
@interface HostCardView ()
|
||||
|
||||
@property (nonatomic, strong) UIView *iconBackgroundView;
|
||||
@property (nonatomic, strong) UIImageView *hostIconView;
|
||||
@property (nonatomic, strong) UILabel *hostNameLabel;
|
||||
@property (nonatomic, strong) UIImageView *statusIcon;
|
||||
@property (nonatomic, strong) UILabel *statusLabel;
|
||||
@property (nonatomic, strong) UIButton *appButton;
|
||||
@property (nonatomic, strong) UIButton *launchButton;
|
||||
@property (nonatomic, strong) UIButton *pairButton;
|
||||
@property (nonatomic, strong) UIButton *wakeupButton;
|
||||
@property (nonatomic, strong) UIButton *transparentButton;
|
||||
@property (nonatomic, strong) NSLayoutConstraint *widthConstraint;
|
||||
@property (nonatomic, strong) NSLayoutConstraint *heightConstraint;
|
||||
@property (nonatomic, strong) UIView *separatorLine;
|
||||
@property (nonatomic, assign) CGFloat cardContentpadding;
|
||||
@property (nonatomic, assign) CGSize size;
|
||||
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation HostCardView {
|
||||
TemporaryHost* _host;
|
||||
UIActivityIndicatorView* _hostSpinner;
|
||||
UIImageView* lockIconView;
|
||||
CGFloat computerIconMonitorCenterYOffset;
|
||||
CGFloat buttonHeight;
|
||||
CGFloat buttonLabelFontSize;
|
||||
CGFloat iconAndButtonSpacing;
|
||||
UIColor *defaultBlue;
|
||||
UIColor *defaultGreen;
|
||||
CAGradientLayer *backgroundLayer;
|
||||
bool longPressFired;
|
||||
}
|
||||
|
||||
static const float REFRESH_CYCLE = 2.0f;
|
||||
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
buttonLabelFontSize = 15*_sizeFactor;
|
||||
longPressFired = false;
|
||||
computerIconMonitorCenterYOffset = -3.3*_sizeFactor;
|
||||
iconAndButtonSpacing = 37*_sizeFactor;
|
||||
buttonHeight = 39*_sizeFactor;
|
||||
defaultBlue = [ThemeManager appPrimaryColor];
|
||||
defaultGreen = [UIColor colorWithRed:52.0/255.0 green:199.0/255.0 blue:89.0/255.0 alpha:1.0];
|
||||
// self.userIterfaceStyle = UIUserInterfaceStyleLight
|
||||
|
||||
UILongPressGestureRecognizer* longPressRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(hostCardLongPressed:)];
|
||||
[self addGestureRecognizer:longPressRecognizer];
|
||||
|
||||
[self createBackgroundLayer];
|
||||
[self setupUI];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id) initWithHost:(TemporaryHost*)host {
|
||||
self.sizeFactor = 1.0;
|
||||
self = [self init];
|
||||
_host = host;
|
||||
|
||||
// Use UIContextMenuInteraction on iOS 13.0+ and a standard UILongPressGestureRecognizer
|
||||
// for tvOS devices and iOS prior to 13.0.
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id) initWithHost:(TemporaryHost*)host andSizeFactor:(CGFloat)sizeFactor {
|
||||
_sizeFactor = sizeFactor;
|
||||
self = [self init];
|
||||
_host = host;
|
||||
|
||||
// Use UIContextMenuInteraction on iOS 13.0+ and a standard UILongPressGestureRecognizer
|
||||
// for tvOS devices and iOS prior to 13.0.
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)coder {
|
||||
self = [super initWithCoder:coder];
|
||||
if (self) {
|
||||
[self setupUI];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)appButtonTapped{
|
||||
if(longPressFired){
|
||||
longPressFired = false;
|
||||
return;
|
||||
}
|
||||
if ([self.delegate respondsToSelector:@selector(appButtonTappedForHost:)]) {
|
||||
[self.delegate appButtonTappedForHost:_host];
|
||||
} else NSLog(@"Delegate not set or does not respond to appButtonTappedForHost:");
|
||||
}
|
||||
|
||||
- (void)launchButtonTapped{
|
||||
if(longPressFired){
|
||||
longPressFired = false;
|
||||
return;
|
||||
}
|
||||
if ([self.delegate respondsToSelector:@selector(launchButtonTappedForHost:)]) {
|
||||
[self.delegate launchButtonTappedForHost:_host];
|
||||
} else NSLog(@"Delegate not set or does not respond to launchButtonTappedForHost:");
|
||||
}
|
||||
|
||||
- (void)wakeupButtonTapped{
|
||||
if(longPressFired){
|
||||
longPressFired = false;
|
||||
return;
|
||||
}
|
||||
if ([self.delegate respondsToSelector:@selector(wakeupButtonTappedForHost:)]) {
|
||||
[self.delegate wakeupButtonTappedForHost:_host];
|
||||
} else NSLog(@"Delegate not set or does not respond to launchButtonTappedForHost:");
|
||||
}
|
||||
|
||||
|
||||
-(void)pairButtonTapped{
|
||||
if(longPressFired){
|
||||
longPressFired = false;
|
||||
return;
|
||||
}
|
||||
if ([self.delegate respondsToSelector:@selector(pairButtonTappedForHost:)]) {
|
||||
[self.delegate pairButtonTappedForHost:_host];
|
||||
} else NSLog(@"Delegate not set or does not respond to pairButtonTappedForHost:");
|
||||
|
||||
}
|
||||
|
||||
- (void)hostCardLongPressed:(UILongPressGestureRecognizer* )gesture{
|
||||
if(gesture.state != UIGestureRecognizerStateBegan) return;
|
||||
longPressFired = true;
|
||||
if ([self.delegate respondsToSelector:@selector(hostCardLongPressed:view:)]) {
|
||||
[self.delegate hostCardLongPressed:_host view:self];
|
||||
} else NSLog(@"Delegate not set or does not respond");
|
||||
}
|
||||
|
||||
- (void)resizeBySizeFactor:(CGFloat)factor{
|
||||
[self.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];
|
||||
self.sizeFactor = factor;
|
||||
[self setupUI];
|
||||
}
|
||||
|
||||
- (void)setupUI {
|
||||
self.userInteractionEnabled = YES;
|
||||
self.backgroundColor = [ThemeManager widgetBackgroundColor]; // theme
|
||||
self.cardContentpadding = 13 * _sizeFactor;
|
||||
self.clipsToBounds = YES;
|
||||
self.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
CGFloat appButtonWidth = 100*_sizeFactor;
|
||||
CGFloat launchButtonWidth = 120*_sizeFactor;
|
||||
CGFloat cardWidth = _cardContentpadding*2 + appButtonWidth + 15*_sizeFactor + launchButtonWidth;
|
||||
self.layer.cornerRadius = 2*(uint16_t)(cardWidth*0.0603/2) ;
|
||||
/*
|
||||
NSLayoutConstraint *topAnchorConstraint;
|
||||
topAnchorConstraint = [self.topAnchor constraintEqualToAnchor:self.superview.topAnchor constant:500];
|
||||
topAnchorConstraint.active = YES;
|
||||
*/
|
||||
_heightConstraint = [self.heightAnchor constraintEqualToConstant:300];
|
||||
_heightConstraint.active = YES;
|
||||
_widthConstraint = [self.widthAnchor constraintEqualToConstant:300];
|
||||
_widthConstraint.active = YES;
|
||||
|
||||
|
||||
// 图标背景
|
||||
self.iconBackgroundView = [[UIView alloc] initWithFrame:CGRectMake(_cardContentpadding, _cardContentpadding, 80*_sizeFactor, 80*_sizeFactor)];
|
||||
self.iconBackgroundView.backgroundColor = defaultBlue;
|
||||
self.iconBackgroundView.layer.cornerRadius = 2*(uint16_t)(20*_sizeFactor/2);
|
||||
[self addSubview:self.iconBackgroundView];
|
||||
|
||||
// 图标图片
|
||||
self.hostIconView = [[UIImageView alloc] init];
|
||||
self.hostIconView.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
self.hostIconView.contentMode = UIViewContentModeScaleAspectFit;
|
||||
if (@available(iOS 13.0, *)) {
|
||||
self.hostIconView.image = [UIImage systemImageNamed:@"display"];
|
||||
} else {
|
||||
self.hostIconView.image = [UIImage imageNamed:@"Computer"];
|
||||
[NSLayoutConstraint activateConstraints:@[
|
||||
[self.hostIconView.heightAnchor constraintEqualToConstant:57*_sizeFactor],
|
||||
[self.hostIconView.widthAnchor constraintEqualToConstant:57*_sizeFactor],
|
||||
]];
|
||||
}
|
||||
self.hostIconView.tintColor = [UIColor whiteColor];
|
||||
[self.iconBackgroundView addSubview:self.hostIconView];
|
||||
[NSLayoutConstraint activateConstraints:@[
|
||||
[self.hostIconView.centerXAnchor constraintEqualToAnchor:self.iconBackgroundView.centerXAnchor constant:0],
|
||||
[self.hostIconView.centerYAnchor constraintEqualToAnchor:self.iconBackgroundView.centerYAnchor constant:0],
|
||||
[self.hostIconView.heightAnchor constraintEqualToConstant:63*_sizeFactor],
|
||||
[self.hostIconView.widthAnchor constraintEqualToConstant:63*_sizeFactor],
|
||||
]];
|
||||
// [self.iconBackgroundView layoutIfNeeded];
|
||||
// [self.iconImageView layoutIfNeeded];
|
||||
|
||||
//spinner
|
||||
bool oldiOS = false;
|
||||
if (@available(iOS 13.0, *)){}
|
||||
else oldiOS = true;
|
||||
|
||||
_hostSpinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
|
||||
_hostSpinner.userInteractionEnabled = NO;
|
||||
_hostSpinner.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
_hostSpinner.hidesWhenStopped = YES;
|
||||
[self.iconBackgroundView addSubview:_hostSpinner];
|
||||
[NSLayoutConstraint activateConstraints:@[
|
||||
[_hostSpinner.centerXAnchor constraintEqualToAnchor:self.iconBackgroundView.centerXAnchor constant:0],
|
||||
[_hostSpinner.centerYAnchor constraintEqualToAnchor:self.iconBackgroundView.centerYAnchor constant:oldiOS ? -5.5*_sizeFactor : computerIconMonitorCenterYOffset],
|
||||
]];
|
||||
_hostSpinner.transform = CGAffineTransformMakeScale(_sizeFactor, _sizeFactor);
|
||||
[_hostSpinner stopAnimating];
|
||||
// [_hostSpinner startAnimating];
|
||||
|
||||
|
||||
// lockIcon
|
||||
lockIconView =[[UIImageView alloc] init];
|
||||
lockIconView.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
if (@available(iOS 13.0, *)) {
|
||||
lockIconView.image = [UIImage systemImageNamed:@"lock.fill"];
|
||||
} else {
|
||||
lockIconView.image = [UIImage imageNamed:@"LockedOverlayIcon"];
|
||||
computerIconMonitorCenterYOffset = -5 * _sizeFactor;
|
||||
}
|
||||
lockIconView.tintColor = [UIColor whiteColor];
|
||||
[self.iconBackgroundView insertSubview:lockIconView aboveSubview:_iconBackgroundView];
|
||||
[NSLayoutConstraint activateConstraints:@[
|
||||
[lockIconView.centerXAnchor constraintEqualToAnchor:_iconBackgroundView.centerXAnchor constant:0],
|
||||
[lockIconView.centerYAnchor constraintEqualToAnchor:_iconBackgroundView.centerYAnchor constant:computerIconMonitorCenterYOffset],
|
||||
[lockIconView.widthAnchor constraintEqualToConstant:22*_sizeFactor],
|
||||
[lockIconView.heightAnchor constraintEqualToConstant:22*_sizeFactor]
|
||||
]];
|
||||
lockIconView.hidden = true;
|
||||
|
||||
// 设备名
|
||||
CGFloat hostNameLabelCoordX = _cardContentpadding+_iconBackgroundView.frame.size.width+16*_sizeFactor;
|
||||
self.hostNameLabel = [[UILabel alloc] initWithFrame:CGRectMake(hostNameLabelCoordX, _cardContentpadding+_iconBackgroundView.frame.size.height*0.02, cardWidth-hostNameLabelCoordX-_cardContentpadding, 30*_sizeFactor)];
|
||||
self.hostNameLabel.numberOfLines = 1;
|
||||
self.hostNameLabel.adjustsFontSizeToFitWidth = YES;
|
||||
self.hostNameLabel.minimumScaleFactor = 0.8; // 最小字体缩放比例
|
||||
self.hostNameLabel.lineBreakMode = NSLineBreakByTruncatingTail;
|
||||
|
||||
self.hostIconView.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
self.hostNameLabel.text = @"RazerBlade 16";
|
||||
self.hostNameLabel.textColor = [UIColor whiteColor]; //theme
|
||||
self.hostNameLabel.font = [UIFont boldSystemFontOfSize:18*_sizeFactor];
|
||||
[self addSubview:self.hostNameLabel];
|
||||
|
||||
// 在线文字
|
||||
self.statusLabel = [[UILabel alloc] initWithFrame:CGRectMake(205, 68, 100, 24)];
|
||||
self.statusLabel.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
self.statusLabel.text = [LocalizationHelper localizedStringForKey:@"Online"];
|
||||
self.statusLabel.font = [UIFont systemFontOfSize:14*_sizeFactor weight:UIFontWeightMedium];
|
||||
self.statusLabel.textColor = defaultGreen;
|
||||
// self.statusLabel.font = [UIFont systemFontOfSize:16*_sizeFactor];
|
||||
// 在线状态图标
|
||||
self.statusIcon = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 16*_sizeFactor, 16*_sizeFactor)];
|
||||
self.statusIcon.contentMode = UIViewContentModeScaleAspectFit;
|
||||
self.statusIcon.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
self.statusIcon.image = [[UIImage imageNamed:@"wifi_green"] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
|
||||
// self.statusIcon.tintColor = defaultGreen;
|
||||
[self addSubview:self.statusIcon];
|
||||
[self addSubview:self.statusLabel];
|
||||
[NSLayoutConstraint activateConstraints:@[
|
||||
[self.statusLabel.leadingAnchor constraintEqualToAnchor:self.statusIcon.trailingAnchor constant:3*_sizeFactor],
|
||||
[self.statusLabel.topAnchor constraintEqualToAnchor:self.hostNameLabel.bottomAnchor constant:0*_sizeFactor],
|
||||
|
||||
[self.statusIcon.centerYAnchor constraintEqualToAnchor:self.statusLabel.centerYAnchor constant:0],
|
||||
[self.statusIcon.leadingAnchor constraintEqualToAnchor:self.hostNameLabel.leadingAnchor],
|
||||
[self.statusIcon.widthAnchor constraintEqualToConstant:16*_sizeFactor]
|
||||
]];
|
||||
|
||||
// 启动应用按钮
|
||||
self.appButton = [UIButton buttonWithType:UIButtonTypeSystem];
|
||||
self.appButton.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
self.appButton.frame = CGRectMake(20, 200, 150, buttonHeight);
|
||||
[self.appButton setTitle:[LocalizationHelper localizedStringForKey:@"Applications"] forState:UIControlStateNormal];
|
||||
[self.appButton setTitleColor:[ThemeManager textColorGray] forState:UIControlStateNormal]; //theme
|
||||
self.appButton.titleLabel.font = [UIFont systemFontOfSize:buttonLabelFontSize];
|
||||
[self.appButton addTarget:self action:@selector(appButtonTapped) forControlEvents:UIControlEventPrimaryActionTriggered];
|
||||
[self addSubview:self.appButton];
|
||||
[NSLayoutConstraint activateConstraints:@[
|
||||
[self.appButton.leadingAnchor constraintEqualToAnchor:self.iconBackgroundView.leadingAnchor constant:0],
|
||||
[self.appButton.topAnchor constraintEqualToAnchor:self.iconBackgroundView.bottomAnchor constant:iconAndButtonSpacing],
|
||||
[self.appButton.widthAnchor constraintEqualToConstant:appButtonWidth],
|
||||
[self.appButton.heightAnchor constraintEqualToConstant:buttonHeight],
|
||||
// [self.launchButton.titleLabel.leadingAnchor constraintEqualToAnchor:self.launchButton.imageView.trailingAnchor constant:3*_sizeFactor]
|
||||
]];
|
||||
|
||||
|
||||
// 开始串流按钮
|
||||
self.launchButton = [UIButton buttonWithType:UIButtonTypeSystem];
|
||||
self.launchButton.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
self.launchButton.frame = CGRectMake(0, 0, 150, 50);
|
||||
self.launchButton.backgroundColor = defaultBlue;
|
||||
|
||||
self.launchButton.layer.cornerRadius = 2*(uint16_t)(cardWidth*0.0377/2);
|
||||
[self.launchButton setTitle:[LocalizationHelper localizedStringForKey:@" Launch"] forState:UIControlStateNormal];
|
||||
[self.launchButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; // theme
|
||||
self.launchButton.titleLabel.font = [UIFont boldSystemFontOfSize:buttonLabelFontSize];
|
||||
self.launchButton.tintColor = [UIColor whiteColor];
|
||||
[self.launchButton addTarget:self action:@selector(launchButtonTapped) forControlEvents:UIControlEventPrimaryActionTriggered];
|
||||
[self addSubview:self.launchButton];
|
||||
if (@available(iOS 13.0, *)) {
|
||||
UIImageSymbolConfiguration *config = [UIImageSymbolConfiguration configurationWithPointSize:buttonHeight*0.263];
|
||||
[self.launchButton setImage:[UIImage systemImageNamed:@"play.fill" withConfiguration:config] forState:UIControlStateNormal];
|
||||
|
||||
} else {
|
||||
// Fallback on earlier versions
|
||||
}
|
||||
|
||||
|
||||
[NSLayoutConstraint activateConstraints:@[
|
||||
[self.launchButton.leadingAnchor constraintEqualToAnchor:self.appButton.trailingAnchor constant:15*_sizeFactor],
|
||||
[self.launchButton.centerYAnchor constraintEqualToAnchor:self.appButton.centerYAnchor constant:0],
|
||||
[self.launchButton.widthAnchor constraintEqualToConstant:launchButtonWidth],
|
||||
[self.launchButton.heightAnchor constraintEqualToConstant:buttonHeight],
|
||||
// [self.launchButton.titleLabel.leadingAnchor constraintEqualToAnchor:self.launchButton.imageView.trailingAnchor constant:3*_sizeFactor]
|
||||
]];
|
||||
|
||||
// 配对按钮
|
||||
self.pairButton = [UIButton buttonWithType:UIButtonTypeSystem];
|
||||
self.pairButton.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
self.pairButton.frame = CGRectMake(0, 0, 150, 50);
|
||||
self.pairButton.backgroundColor = [ThemeManager textTintColorWithAlpha];
|
||||
self.pairButton.layer.cornerRadius = 2*(uint16_t)(cardWidth*0.0377/2);
|
||||
[self.pairButton setTitle:[LocalizationHelper localizedStringForKey:@" Pair with PIN"] forState:UIControlStateNormal];
|
||||
[self.pairButton setTitleColor:defaultBlue forState:UIControlStateNormal]; // theme
|
||||
self.pairButton.titleLabel.font = [UIFont boldSystemFontOfSize:buttonLabelFontSize];
|
||||
if (@available(iOS 13.0, *)) {
|
||||
UIImageSymbolConfiguration *config = [UIImageSymbolConfiguration configurationWithPointSize:buttonHeight/3.4 weight:UIImageSymbolWeightBold];
|
||||
UIImage *templateImage = [UIImage systemImageNamed:@"lock.open.fill" withConfiguration:config];
|
||||
UIImage *coloredImage = [templateImage imageWithTintColor:defaultBlue renderingMode:UIImageRenderingModeAlwaysOriginal];
|
||||
[self.pairButton setImage:coloredImage forState:UIControlStateNormal];
|
||||
} else {
|
||||
// Fallback on earlier versions
|
||||
}
|
||||
self.pairButton.backgroundColor = [ThemeManager textTintColorWithAlpha];
|
||||
[self.pairButton setTitleColor:defaultBlue forState:UIControlStateNormal]; // theme
|
||||
|
||||
[self.pairButton addTarget:self action:@selector(pairButtonTapped) forControlEvents:UIControlEventPrimaryActionTriggered];
|
||||
[self addSubview:self.pairButton];
|
||||
|
||||
[NSLayoutConstraint activateConstraints:@[
|
||||
[self.pairButton.leadingAnchor constraintEqualToAnchor:self.leadingAnchor constant:_cardContentpadding],
|
||||
[self.pairButton.trailingAnchor constraintEqualToAnchor:self.trailingAnchor constant:-_cardContentpadding],
|
||||
[self.pairButton.topAnchor constraintEqualToAnchor:self.iconBackgroundView.bottomAnchor constant:iconAndButtonSpacing],
|
||||
// [self.pairButton.widthAnchor constraintEqualToConstant:launchButtonWidth],
|
||||
[self.pairButton.heightAnchor constraintEqualToConstant:buttonHeight],
|
||||
]];
|
||||
|
||||
|
||||
|
||||
|
||||
// 唤醒按钮
|
||||
self.wakeupButton = [UIButton buttonWithType:UIButtonTypeSystem];
|
||||
self.wakeupButton.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
self.wakeupButton.frame = CGRectMake(0, 0, 150, 50);
|
||||
self.wakeupButton.backgroundColor = [ThemeManager textTintColorWithAlpha];
|
||||
self.wakeupButton.layer.cornerRadius = 2*(uint16_t)(cardWidth*0.0377/2);
|
||||
[self.wakeupButton setTitle:[LocalizationHelper localizedStringForKey:@" Wake-on-LAN"] forState:UIControlStateNormal];
|
||||
[self.wakeupButton setTitleColor:defaultBlue forState:UIControlStateNormal]; // theme
|
||||
self.wakeupButton.titleLabel.font = [UIFont boldSystemFontOfSize:buttonLabelFontSize];
|
||||
[self.wakeupButton addTarget:self action:@selector(wakeupButtonTapped) forControlEvents:UIControlEventPrimaryActionTriggered];
|
||||
[self addSubview:self.wakeupButton];
|
||||
|
||||
[NSLayoutConstraint activateConstraints:@[
|
||||
[self.wakeupButton.leadingAnchor constraintEqualToAnchor:self.leadingAnchor constant:_cardContentpadding],
|
||||
[self.wakeupButton.trailingAnchor constraintEqualToAnchor:self.trailingAnchor constant:-_cardContentpadding],
|
||||
[self.wakeupButton.topAnchor constraintEqualToAnchor:self.iconBackgroundView.bottomAnchor constant:iconAndButtonSpacing],
|
||||
// [self.pairButton.widthAnchor constraintEqualToConstant:launchButtonWidth],
|
||||
[self.wakeupButton.heightAnchor constraintEqualToConstant:buttonHeight],
|
||||
]];
|
||||
|
||||
|
||||
|
||||
//分隔线
|
||||
_separatorLine = [[UIView alloc] init];
|
||||
_separatorLine.backgroundColor = [UIColor colorWithWhite:0.3 alpha:5.0];
|
||||
_separatorLine.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
[self addSubview:_separatorLine];
|
||||
[NSLayoutConstraint activateConstraints:@[
|
||||
[_separatorLine.leadingAnchor constraintEqualToAnchor:self.leadingAnchor constant:_cardContentpadding],
|
||||
[_separatorLine.trailingAnchor constraintEqualToAnchor:self.trailingAnchor constant:-_cardContentpadding],
|
||||
[_separatorLine.centerYAnchor constraintEqualToAnchor:_iconBackgroundView.bottomAnchor constant: iconAndButtonSpacing/2],
|
||||
[_separatorLine.heightAnchor constraintEqualToConstant:1.0/UIScreen.mainScreen.scale]
|
||||
]];
|
||||
|
||||
// 透明按钮
|
||||
self.transparentButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
// self.transparentButton.frame = CGRectMake(50, 100, 100, 100); // 设置点击区域
|
||||
self.transparentButton.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
// 设置透明背景和无内容
|
||||
self.transparentButton.backgroundColor = [UIColor clearColor];
|
||||
[self.transparentButton setTitle:@"" forState:UIControlStateNormal];
|
||||
[self.transparentButton setImage:nil forState:UIControlStateNormal];
|
||||
[self.transparentButton addTarget:self action:@selector(appButtonTapped) forControlEvents:UIControlEventPrimaryActionTriggered];
|
||||
|
||||
[self addSubview:self.transparentButton];
|
||||
[NSLayoutConstraint activateConstraints:@[
|
||||
[self.transparentButton.leadingAnchor constraintEqualToAnchor:self.leadingAnchor constant:0],
|
||||
[self.transparentButton.trailingAnchor constraintEqualToAnchor:self.trailingAnchor constant:0],
|
||||
[self.transparentButton.topAnchor constraintEqualToAnchor:self.topAnchor constant:0],
|
||||
[self.transparentButton.bottomAnchor constraintEqualToAnchor:self.separatorLine.topAnchor],
|
||||
]];
|
||||
|
||||
|
||||
_widthConstraint.constant = _cardContentpadding*2 + appButtonWidth + 15*_sizeFactor + launchButtonWidth;
|
||||
_heightConstraint.constant = _cardContentpadding*2 + _iconBackgroundView.frame.size.height + iconAndButtonSpacing + buttonHeight + 1;
|
||||
|
||||
_size = CGSizeMake(_widthConstraint.constant, _heightConstraint.constant);
|
||||
|
||||
[self updateTheme:ThemeManager.userInterfaceStyle];
|
||||
}
|
||||
|
||||
- (void)createBackgroundLayer{
|
||||
backgroundLayer = [CAGradientLayer layer];
|
||||
UIColor *gradientColorDark = [UIColor colorWithRed:0.0 green:0.319 blue:0.64 alpha:1.0];
|
||||
UIColor *gradientColorLight = [gradientColorDark colorWithAlphaComponent:0.52];
|
||||
CGColorRef gradientColorRef = ThemeManager.userInterfaceStyle == UIUserInterfaceStyleDark ? gradientColorDark.CGColor : gradientColorLight.CGColor;
|
||||
backgroundLayer.colors = @[
|
||||
(__bridge id)[UIColor clearColor].CGColor,
|
||||
(__bridge id)[UIColor clearColor].CGColor,
|
||||
(__bridge id)[UIColor clearColor].CGColor,
|
||||
(__bridge id)gradientColorRef
|
||||
];
|
||||
|
||||
backgroundLayer.locations = @[@0, @0.18, @0.5, @1];
|
||||
backgroundLayer.startPoint = CGPointMake(0.25, 0.5);
|
||||
backgroundLayer.endPoint = CGPointMake(0.75, 0.5);
|
||||
|
||||
CGAffineTransform transform = CGAffineTransformMake(-1.01, -1, 1, -3.67, 0.5, 2.83);
|
||||
backgroundLayer.transform = CATransform3DMakeAffineTransform(transform);
|
||||
[self.layer insertSublayer:backgroundLayer atIndex:0];
|
||||
}
|
||||
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
backgroundLayer.bounds = CGRectInset(self.bounds, -0.5 * self.bounds.size.width, -0.5 * self.bounds.size.height);
|
||||
backgroundLayer.position = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
|
||||
[self updateContentsForHost:_host]; // quick update before looping
|
||||
}
|
||||
|
||||
- (void)tintAdjustmentModeDidChange {
|
||||
// [super tintAdjustmentModeDidChange];
|
||||
NSLog(@"tintChanged........");
|
||||
self.tintAdjustmentMode = UIViewTintAdjustmentModeNormal;
|
||||
}
|
||||
|
||||
|
||||
- (void)updateTheme:(UIUserInterfaceStyle)userIterfaceStyle{
|
||||
self.backgroundColor = [ThemeManager widgetBackgroundColor];
|
||||
_hostNameLabel.textColor = [ThemeManager textColor];
|
||||
[_appButton setTitleColor:[ThemeManager appPrimaryColor] forState:UIControlStateNormal];
|
||||
_separatorLine.backgroundColor = [ThemeManager separatorColor];
|
||||
backgroundLayer.hidden = NO;
|
||||
[self updateContentsForHost:_host];
|
||||
}
|
||||
|
||||
- (void)didMoveToSuperview {
|
||||
// Start our update loop when we are added to our cell
|
||||
if (self.superview != nil && _host != nil) {
|
||||
NSLog(@"start update loop");
|
||||
[self updateLoop];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection {
|
||||
[super traitCollectionDidChange:previousTraitCollection];
|
||||
|
||||
if (@available(iOS 13.0, *)) {
|
||||
if ([self.traitCollection hasDifferentColorAppearanceComparedToTraitCollection:previousTraitCollection]) {
|
||||
[self updateTheme:self.traitCollection.userInterfaceStyle];
|
||||
}
|
||||
} else {
|
||||
[self updateTheme:UIUserInterfaceStyleDark];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
- (void) updateLoop {
|
||||
// Stop immediately if the view has been detached
|
||||
if (self.superview == nil) {
|
||||
return;
|
||||
}
|
||||
|
||||
[self updateContentsForHost:_host];
|
||||
|
||||
// Queue the next refresh cycle
|
||||
[self performSelector:@selector(updateLoop) withObject:self afterDelay:REFRESH_CYCLE];
|
||||
}
|
||||
|
||||
- (void) updateContentsForHost:(TemporaryHost*)host {
|
||||
_hostNameLabel.text = host.name;
|
||||
backgroundLayer.hidden = !(host.state == StateOnline && host.pairState == PairStatePaired);
|
||||
|
||||
switch (host.state) {
|
||||
case StateOnline:
|
||||
[_hostSpinner stopAnimating];
|
||||
_statusLabel.textColor = defaultGreen;
|
||||
_statusLabel.text = [LocalizationHelper localizedStringForKey:@"Online"];
|
||||
self.statusIcon.image = [[UIImage imageNamed:@"wifi_green"] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
|
||||
self.statusIcon.hidden = NO;
|
||||
_hostIconView.tintColor = [UIColor whiteColor];
|
||||
_appButton.titleLabel.font = [UIFont systemFontOfSize:buttonLabelFontSize];
|
||||
if(host.pairState == PairStatePaired){
|
||||
_iconBackgroundView.backgroundColor = defaultBlue;
|
||||
lockIconView.hidden = YES;
|
||||
[_appButton setTitle:[LocalizationHelper localizedStringForKey:@"Applications"] forState:UIControlStateNormal];
|
||||
[_launchButton setTitle:[LocalizationHelper localizedStringForKey:@" Launch"] forState:UIControlStateNormal];
|
||||
[_appButton setEnabled:YES];
|
||||
[_launchButton setEnabled:YES];
|
||||
_appButton.hidden = NO;
|
||||
_launchButton.hidden = NO;
|
||||
_pairButton.hidden = YES;
|
||||
_wakeupButton.hidden = YES;
|
||||
|
||||
[_appButton setTitleColor:defaultBlue forState:UIControlStateNormal]; // theme
|
||||
if (@available(iOS 13.0, *)) {
|
||||
UIImageSymbolConfiguration *config = [UIImageSymbolConfiguration configurationWithPointSize:buttonHeight*0.263];
|
||||
[self.launchButton setImage:[UIImage systemImageNamed:@"play.fill" withConfiguration:config] forState:UIControlStateNormal];
|
||||
} else {
|
||||
}
|
||||
_launchButton.backgroundColor = defaultBlue;
|
||||
[_launchButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; // theme
|
||||
}
|
||||
else {
|
||||
_iconBackgroundView.backgroundColor = [ThemeManager appPrimaryColorWithAlpha];
|
||||
lockIconView.hidden = NO;
|
||||
_appButton.hidden = YES;
|
||||
_launchButton.hidden = YES;
|
||||
_pairButton.hidden = NO;
|
||||
_wakeupButton.hidden = YES;
|
||||
}
|
||||
break;
|
||||
case StateOffline:
|
||||
[_hostSpinner stopAnimating];
|
||||
// _iconBackgroundView.backgroundColor = _userIterfaceStyle == UIUserInterfaceStyleDark ? [UIColor appBackgroundColorDark] : [UIColor appBackgroundColorLight];
|
||||
_iconBackgroundView.backgroundColor = [ThemeManager appBackgroundColor];
|
||||
_statusLabel.textColor = [ThemeManager textColorGray];
|
||||
_statusLabel.text = [LocalizationHelper localizedStringForKey: @"Offline"];
|
||||
_statusIcon.tintColor = [ThemeManager textColorGray];
|
||||
if (@available(iOS 13.0, *)) {
|
||||
_statusIcon.image = [UIImage systemImageNamed:@"exclamationmark.triangle.fill"];
|
||||
} else {
|
||||
self.statusIcon.hidden = YES;
|
||||
}
|
||||
_hostIconView.tintColor = [ThemeManager lowProfileGray];
|
||||
lockIconView.hidden = YES;
|
||||
_appButton.hidden = YES;
|
||||
_launchButton.hidden = YES;
|
||||
_pairButton.hidden = YES;
|
||||
_wakeupButton.hidden = NO;
|
||||
|
||||
if (@available(iOS 13.0, *)) {
|
||||
UIImageSymbolConfiguration *config = [UIImageSymbolConfiguration configurationWithPointSize:buttonHeight/3.45 weight:UIImageSymbolWeightBold];
|
||||
UIImage *templateImage = [UIImage systemImageNamed:@"power" withConfiguration:config];
|
||||
UIImage *coloredImage = [templateImage imageWithTintColor:defaultBlue renderingMode:UIImageRenderingModeAlwaysOriginal];
|
||||
[self.wakeupButton setImage:coloredImage forState:UIControlStateNormal];
|
||||
} else {
|
||||
// Fallback on earlier versions
|
||||
}
|
||||
self.wakeupButton.backgroundColor = [ThemeManager textTintColorWithAlpha];
|
||||
[self.wakeupButton setTitleColor:defaultBlue forState:UIControlStateNormal];
|
||||
break;
|
||||
case StateUnknown:
|
||||
_hostSpinner.color = [UIColor whiteColor];
|
||||
[_hostSpinner startAnimating];
|
||||
// _iconBackgroundView.backgroundColor = _userIterfaceStyle == UIUserInterfaceStyleDark ? [UIColor appBackgroundColorDark] : [UIColor appBackgroundColorLight];
|
||||
_iconBackgroundView.backgroundColor = [ThemeManager appPrimaryColorWithAlpha];
|
||||
_statusLabel.textColor = [ThemeManager textColorGray];
|
||||
_statusLabel.text = [LocalizationHelper localizedStringForKey: @"Detecting..."];
|
||||
_statusIcon.tintColor = [ThemeManager textColorGray];
|
||||
if (@available(iOS 13.0, *)) {
|
||||
_statusIcon.image = [UIImage systemImageNamed:@"antenna.radiowaves.left.and.right"];
|
||||
} else {
|
||||
self.statusIcon.hidden = YES;
|
||||
}
|
||||
// _hostIconView.tintColor = [UIColor lowProfileGray];
|
||||
_hostIconView.tintColor = defaultBlue;
|
||||
lockIconView.hidden = YES;
|
||||
_appButton.hidden = YES;
|
||||
_launchButton.hidden = YES;
|
||||
_pairButton.hidden = YES;
|
||||
_wakeupButton.hidden = NO;
|
||||
|
||||
[_wakeupButton setTitleColor:[ThemeManager textColorGray] forState:UIControlStateNormal]; // theme
|
||||
if (@available(iOS 13.0, *)) {
|
||||
UIImageSymbolConfiguration *config = [UIImageSymbolConfiguration configurationWithPointSize:buttonHeight/3.45 weight:UIImageSymbolWeightBold];
|
||||
UIImage *templateImage = [UIImage systemImageNamed:@"power" withConfiguration:config];
|
||||
UIImage *coloredImage = [templateImage imageWithTintColor:[ThemeManager textColorGray] renderingMode:UIImageRenderingModeAlwaysOriginal];
|
||||
[self.wakeupButton setImage:coloredImage forState:UIControlStateNormal];
|
||||
} else {
|
||||
// Fallback on earlier versions
|
||||
}
|
||||
self.wakeupButton.backgroundColor = [[ThemeManager textColorGray] colorWithAlphaComponent:0.2];
|
||||
[self.wakeupButton setTitleColor:[ThemeManager textColorGray] forState:UIControlStateNormal];
|
||||
break;
|
||||
default:break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "a_button_80x80@2x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "a_button_120x120@3x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 1.0 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "a_button_HD.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 5.1 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "add.pdf",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
%PDF-1.5
|
||||
%µí®û
|
||||
4 0 obj
|
||||
<< /Length 5 0 R
|
||||
/Filter /FlateDecode
|
||||
>>
|
||||
stream
|
||||
xœ]P[à ûÏ)r�1}°cìÒº�öcÛý¥AèZ!%q였°/ç"%Èè9oô&áz>_ž—/%qaä!ºyä�§T3ÐjqµîaÚ”t„,~Dû›ÖÚGHýôzQõ␍®î:xgµ†I6Âß:gP(( hñ¯-1Îü5ç¶Žn3»!&úÖ"˜B-Õ¨~1–@½ZÒ¹](¡¿d÷ë9ا-žt§å…aL
|
||||
endstream
|
||||
endobj
|
||||
5 0 obj
|
||||
183
|
||||
endobj
|
||||
3 0 obj
|
||||
<<
|
||||
/ExtGState <<
|
||||
/a0 << /CA 1 /ca 1 >>
|
||||
>>
|
||||
>>
|
||||
endobj
|
||||
2 0 obj
|
||||
<< /Type /Page % 1
|
||||
/Parent 1 0 R
|
||||
/MediaBox [ 0 0 150 150 ]
|
||||
/Contents 4 0 R
|
||||
/Group <<
|
||||
/Type /Group
|
||||
/S /Transparency
|
||||
/I true
|
||||
/CS /DeviceRGB
|
||||
>>
|
||||
/Resources 3 0 R
|
||||
>>
|
||||
endobj
|
||||
1 0 obj
|
||||
<< /Type /Pages
|
||||
/Kids [ 2 0 R ]
|
||||
/Count 1
|
||||
>>
|
||||
endobj
|
||||
6 0 obj
|
||||
<< /Producer (cairo 1.16.0 (https://cairographics.org))
|
||||
/CreationDate (D:20190922201812-07'00)
|
||||
>>
|
||||
endobj
|
||||
7 0 obj
|
||||
<< /Type /Catalog
|
||||
/Pages 1 0 R
|
||||
>>
|
||||
endobj
|
||||
xref
|
||||
0 8
|
||||
0000000000 65535 f
|
||||
0000000587 00000 n
|
||||
0000000369 00000 n
|
||||
0000000297 00000 n
|
||||
0000000015 00000 n
|
||||
0000000275 00000 n
|
||||
0000000652 00000 n
|
||||
0000000768 00000 n
|
||||
trailer
|
||||
<< /Size 8
|
||||
/Root 7 0 R
|
||||
/Info 6 0 R
|
||||
>>
|
||||
startxref
|
||||
820
|
||||
%%EOF
|
||||
|
Before Width: | Height: | Size: 2.4 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "AltIcon.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 32 KiB |
@@ -1,112 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "20x20",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "20x20",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-29x29@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-29x29@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-40x40@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-40x40@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "60x60",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-60x60@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "60x60",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-60x60@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "20x20",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "20x20",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-29x29@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-29x29@2x-1.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-40x40@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-40x40@2x-1.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "76x76",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-76x76@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "76x76",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-76x76@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "83.5x83.5",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-83.5x83.5@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "1024x1024",
|
||||
"idiom" : "ios-marketing",
|
||||
"filename" : "1024x icon.png",
|
||||
"scale" : "1x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 8.5 KiB |
|
Before Width: | Height: | Size: 9.5 KiB |
@@ -1,25 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "arrow_uturn_backward_square-80x80@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "arrow_uturn_backward_square-120x120@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"template-rendering-intent" : "template"
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 2.0 KiB |
@@ -1,25 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "arrow_uturn_backward_square_fill-80x80@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "arrow_uturn_backward_square_fill-120x120.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"template-rendering-intent" : "template"
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 1.7 KiB |
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "b_button_80x80@2x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "b_button_120x120@3x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 6.3 KiB |
|
Before Width: | Height: | Size: 1.0 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "b_button_HD.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 4.9 KiB |
@@ -1,25 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "chevron_compact_down@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "chevron_compact_down@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"template-rendering-intent" : "template"
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 848 B |
|
Before Width: | Height: | Size: 1.2 KiB |
@@ -1,25 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "chevron_compact_up@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "chevron_compact_up@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"template-rendering-intent" : "template"
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 833 B |
|
Before Width: | Height: | Size: 1.2 KiB |
@@ -1,71 +0,0 @@
|
||||
%PDF-1.5
|
||||
%µí®û
|
||||
4 0 obj
|
||||
<< /Length 5 0 R
|
||||
/Filter /FlateDecode
|
||||
>>
|
||||
stream
|
||||
xœ]‘[ƒ Eÿ³Šl )y¸Œ.¡ÃTû¡µûŸi€ Õa9×›�ÑȺ±l.zÌ+|ÀTm›ñþ48a”ä_°äÑ&&ë‘SØ^8cYÕÍÅíl"‹«Æ5ºá‚v¤�’"{
|
||||
ò<9¶�j�7Ö!Db ×,Ú�–PÊbåÐjt¤•øzÅÚœÔ:ƒº,'á½â‚ýÒ‡ wRÁù‘Ê|µ8)’w
|
||||
-{GÍÑsµ™�æüÞG^’íosû»£MÅkäøà=h_
|
||||
endstream
|
||||
endobj
|
||||
5 0 obj
|
||||
214
|
||||
endobj
|
||||
3 0 obj
|
||||
<<
|
||||
/ExtGState <<
|
||||
/a0 << /CA 0.3 /ca 0.3 >>
|
||||
/a1 << /CA 1 /ca 1 >>
|
||||
>>
|
||||
>>
|
||||
endobj
|
||||
2 0 obj
|
||||
<< /Type /Page % 1
|
||||
/Parent 1 0 R
|
||||
/MediaBox [ 0 0 375 375 ]
|
||||
/Contents 4 0 R
|
||||
/Group <<
|
||||
/Type /Group
|
||||
/S /Transparency
|
||||
/I true
|
||||
/CS /DeviceRGB
|
||||
>>
|
||||
/Resources 3 0 R
|
||||
>>
|
||||
endobj
|
||||
1 0 obj
|
||||
<< /Type /Pages
|
||||
/Kids [ 2 0 R ]
|
||||
/Count 1
|
||||
>>
|
||||
endobj
|
||||
6 0 obj
|
||||
<< /Producer (cairo 1.16.0 (https://cairographics.org))
|
||||
/CreationDate (D:20190922201805-07'00)
|
||||
>>
|
||||
endobj
|
||||
7 0 obj
|
||||
<< /Type /Catalog
|
||||
/Pages 1 0 R
|
||||
>>
|
||||
endobj
|
||||
xref
|
||||
0 8
|
||||
0000000000 65535 f
|
||||
0000000650 00000 n
|
||||
0000000432 00000 n
|
||||
0000000328 00000 n
|
||||
0000000015 00000 n
|
||||
0000000306 00000 n
|
||||
0000000715 00000 n
|
||||
0000000831 00000 n
|
||||
trailer
|
||||
<< /Size 8
|
||||
/Root 7 0 R
|
||||
/Info 6 0 R
|
||||
>>
|
||||
startxref
|
||||
883
|
||||
%%EOF
|
||||
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "Computer.pdf",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ControlIcon.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 2.7 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "DeleteIcon.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 2.6 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "doneIcon.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 405 B |
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "down_61x75@2x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "down_93x114@3x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 455 B |
|
Before Width: | Height: | Size: 3.6 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "DownButtonHD.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 2.9 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "error.pdf",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
%PDF-1.5
|
||||
%µí®û
|
||||
4 0 obj
|
||||
<< /Length 5 0 R
|
||||
/Filter /FlateDecode
|
||||
>>
|
||||
stream
|
||||
xœUMA€ »ïý€¸� ßð ÆD=ÈAý"L4š&[»µ›€3ÉE<cJ´“ àXÐŽŒå¤`¬‡8)-A:gúWo(ÜšÂVŠ:Õ !ª±n«bμú”?©ØB*žL¸Ü¶ðÿUS3␍tH(å
|
||||
endstream
|
||||
endobj
|
||||
5 0 obj
|
||||
110
|
||||
endobj
|
||||
3 0 obj
|
||||
<<
|
||||
/ExtGState <<
|
||||
/a0 << /CA 1 /ca 1 >>
|
||||
>>
|
||||
>>
|
||||
endobj
|
||||
2 0 obj
|
||||
<< /Type /Page % 1
|
||||
/Parent 1 0 R
|
||||
/MediaBox [ 0 0 150 150 ]
|
||||
/Contents 4 0 R
|
||||
/Group <<
|
||||
/Type /Group
|
||||
/S /Transparency
|
||||
/I true
|
||||
/CS /DeviceRGB
|
||||
>>
|
||||
/Resources 3 0 R
|
||||
>>
|
||||
endobj
|
||||
1 0 obj
|
||||
<< /Type /Pages
|
||||
/Kids [ 2 0 R ]
|
||||
/Count 1
|
||||
>>
|
||||
endobj
|
||||
6 0 obj
|
||||
<< /Producer (cairo 1.16.0 (https://cairographics.org))
|
||||
/CreationDate (D:20190922201819-07'00)
|
||||
>>
|
||||
endobj
|
||||
7 0 obj
|
||||
<< /Type /Catalog
|
||||
/Pages 1 0 R
|
||||
>>
|
||||
endobj
|
||||
xref
|
||||
0 8
|
||||
0000000000 65535 f
|
||||
0000000514 00000 n
|
||||
0000000296 00000 n
|
||||
0000000224 00000 n
|
||||
0000000015 00000 n
|
||||
0000000202 00000 n
|
||||
0000000579 00000 n
|
||||
0000000695 00000 n
|
||||
trailer
|
||||
<< /Size 8
|
||||
/Root 7 0 R
|
||||
/Info 6 0 R
|
||||
>>
|
||||
startxref
|
||||
747
|
||||
%%EOF
|
||||
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "EscapeIcon.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 2.8 KiB |
@@ -1,23 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x",
|
||||
"filename" : "L1_40x40@1x.png"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x",
|
||||
"filename" : "L1_80x80@2x.png"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x",
|
||||
"filename" : "L1_120x120@3x.png"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 6.3 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |