From 796fcd6063766913b5776bee61a5a786674e2bd9 Mon Sep 17 00:00:00 2001 From: ethanarbuckle Date: Tue, 19 Jan 2021 02:10:41 -0800 Subject: [PATCH] add dock alignment preference --- carplayenableprefs/CPAppListController.h | 1 - carplayenableprefs/CPAppListController.m | 39 ++---------- carplayenableprefs/CRERootListController.m | 28 ++++++++- carplayenableprefs/Makefile | 2 +- src/CRCarplayWindow.h | 1 + src/CRCarplayWindow.mm | 48 +++++++++++++-- src/CRPreferences.h | 22 +++++++ src/CRPreferences.mm | 69 ++++++++++++++++++++++ src/common.h | 2 + src/hooks/CarPlay.xm | 14 ++--- src/hooks/SpringBoard.xm | 9 --- 11 files changed, 171 insertions(+), 64 deletions(-) create mode 100644 src/CRPreferences.h create mode 100644 src/CRPreferences.mm diff --git a/carplayenableprefs/CPAppListController.h b/carplayenableprefs/CPAppListController.h index 110b283..e5e7cc0 100644 --- a/carplayenableprefs/CPAppListController.h +++ b/carplayenableprefs/CPAppListController.h @@ -4,7 +4,6 @@ @property (nonatomic, retain) UITableView *rootTable; @property (nonatomic, retain) NSArray *appList; -@property (nonatomic, retain) NSDictionary *cachedPreferences; - (id)initWithAppList:(NSArray *)appList; diff --git a/carplayenableprefs/CPAppListController.m b/carplayenableprefs/CPAppListController.m index b38a84c..3aec636 100644 --- a/carplayenableprefs/CPAppListController.m +++ b/carplayenableprefs/CPAppListController.m @@ -13,8 +13,6 @@ - (id)initWithAppList:(NSArray *)appList NSArray *sortDescriptors = [NSArray arrayWithObject:nameDescriptor]; _appList = [appList sortedArrayUsingDescriptors:sortDescriptors]; - [self reloadPreferences]; - _rootTable = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, [[UIScreen mainScreen] bounds].size.width, [[UIScreen mainScreen] bounds].size.height) style:UITableViewStyleGrouped]; [_rootTable setDelegate:self]; [_rootTable setDataSource:self]; @@ -24,39 +22,15 @@ - (id)initWithAppList:(NSArray *)appList return self; } -- (void)reloadPreferences -{ - if ([[NSFileManager defaultManager] fileExistsAtPath:PREFERENCES_PLIST_PATH]) - { - _cachedPreferences = [[NSDictionary alloc] initWithContentsOfFile:PREFERENCES_PLIST_PATH]; - } - else { - _cachedPreferences = @{}; - } -} - - (BOOL)isAppCarplayEnabled:(NSString *)identifier -{ - if (_cachedPreferences && [_cachedPreferences valueForKey:@"excludedApps"]) - { - NSArray *excludedIdentifiers = _cachedPreferences[@"excludedApps"]; - return [excludedIdentifiers containsObject:identifier] == NO; - } - - return YES; +{ + NSArray *excludedApps = [[CRPreferences sharedInstance] excludedApplications]; + return [excludedApps containsObject:identifier] == NO; } - (void)setApp:(NSString *)identifier shouldBeExcluded:(BOOL)shouldExclude { - NSMutableArray *excludedApps; - if (_cachedPreferences && [_cachedPreferences valueForKey:@"excludedApps"]) - { - excludedApps = [_cachedPreferences[@"excludedApps"] mutableCopy]; - } - else - { - excludedApps = [[NSMutableArray alloc] init]; - } + NSMutableArray *excludedApps = [[[CRPreferences sharedInstance] excludedApplications] mutableCopy]; BOOL alreadyExcluded = [excludedApps containsObject:identifier]; if (shouldExclude && !alreadyExcluded) @@ -68,10 +42,7 @@ - (void)setApp:(NSString *)identifier shouldBeExcluded:(BOOL)shouldExclude [excludedApps removeObject:identifier]; } - NSMutableDictionary *updatedPrefs = [_cachedPreferences mutableCopy]; - updatedPrefs[@"excludedApps"] = excludedApps; - _cachedPreferences = updatedPrefs; - [_cachedPreferences writeToFile:PREFERENCES_PLIST_PATH atomically:NO]; + [[CRPreferences sharedInstance] updateValue:excludedApps forPreferenceKey:@"excludedApps"]; // Notify CarPlay of the changes [[objc_getClass("NSDistributedNotificationCenter") defaultCenter] postNotification:[NSNotification notificationWithName:PREFERENCES_CHANGED_NOTIFICATION object:kPrefsAppLibraryChanged]]; diff --git a/carplayenableprefs/CRERootListController.m b/carplayenableprefs/CRERootListController.m index 749c0a9..136bea6 100644 --- a/carplayenableprefs/CRERootListController.m +++ b/carplayenableprefs/CRERootListController.m @@ -19,7 +19,13 @@ - (id)initForContentSize:(CGSize)size [[objc_getClass("NSDistributedNotificationCenter") defaultCenter] addObserverForName:PREFERENCES_APP_DATA_NOTIFICATION object:kPrefsAppDataReceiving queue:notificationQueue usingBlock:^(NSNotification * _Nonnull note) { NSDictionary *data = [note userInfo]; // Create AppList controller - _appListController = [[CPAppListController alloc] initWithAppList:data[@"appList"]]; + NSArray *appList = data[@"appList"]; + if (appList) + { + dispatch_sync(dispatch_get_main_queue(), ^(void) { + _appListController = [[CPAppListController alloc] initWithAppList:appList]; + }); + } }]; // Ask Springboard for the app data. This may have a little delay, so its being fetched before the user opens the App List controller @@ -36,7 +42,7 @@ -(void)viewDidLoad - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { - return 1; + return 2; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section @@ -82,8 +88,17 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N else if (indexPath.row == 2) { [[cell textLabel] setText:@"Auto"]; + + } + + if (indexPath.row == [[CRPreferences sharedInstance] dockAlignment]) + { [cell setAccessoryType:UITableViewCellAccessoryCheckmark]; } + else + { + [cell setAccessoryType:UITableViewCellAccessoryNone]; + } } return cell; @@ -108,7 +123,7 @@ - (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInte switch (section) { case 0: - //return @""; + return @""; case 1: return @"Created by Ethan Arbuckle"; default: @@ -133,11 +148,18 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath break; } case 1: + { + // Save the new setting + [[CRPreferences sharedInstance] updateValue:@(indexPath.row) forPreferenceKey:@"dockAlignment"]; + // Notify CarPlay of the changes + [[objc_getClass("NSDistributedNotificationCenter") defaultCenter] postNotification:[NSNotification notificationWithName:PREFERENCES_CHANGED_NOTIFICATION object:kPrefsDockAlignmentChanged]]; break; + } default: break; } + [_rootTable reloadData]; [tableView deselectRowAtIndexPath:indexPath animated:YES]; } diff --git a/carplayenableprefs/Makefile b/carplayenableprefs/Makefile index 002a0fa..db3c280 100644 --- a/carplayenableprefs/Makefile +++ b/carplayenableprefs/Makefile @@ -5,7 +5,7 @@ include $(THEOS)/makefiles/common.mk BUNDLE_NAME = carplayenableprefs -carplayenableprefs_FILES = $(wildcard *.m) +carplayenableprefs_FILES = $(wildcard *.m) ../src/CRPreferences.mm carplayenableprefs_INSTALL_PATH = /Library/PreferenceBundles carplayenableprefs_FRAMEWORKS = UIKit carplayenableprefs_PRIVATE_FRAMEWORKS = Preferences diff --git a/src/CRCarplayWindow.h b/src/CRCarplayWindow.h index eb807ca..986c988 100644 --- a/src/CRCarplayWindow.h +++ b/src/CRCarplayWindow.h @@ -12,6 +12,7 @@ id getCarplayCADisplay(void); @property (nonatomic, retain) id appViewController; @property (nonatomic, retain) id sceneMonitor; @property (nonatomic, retain) id application; +@property (nonatomic, retain) NSMutableArray *observers; @property (nonatomic) int orientation; @property (nonatomic) BOOL isFullscreen; diff --git a/src/CRCarplayWindow.mm b/src/CRCarplayWindow.mm index 64c305a..475fe7c 100644 --- a/src/CRCarplayWindow.mm +++ b/src/CRCarplayWindow.mm @@ -30,6 +30,10 @@ - (id)initWithBundleIdentifier:(id)identifier { if ((self = [super init])) { + _observers = [[NSMutableArray alloc] init]; + // Update this processes' preference cache + [[CRPreferences sharedInstance] reloadPreferences]; + // Start in landscape self.orientation = 3; @@ -55,6 +59,7 @@ - (id)initWithBundleIdentifier:(id)identifier [self.rootWindow.layer setCornerRadius:13.0f]; [self.rootWindow.layer setMasksToBounds:YES]; + [self setupWallpaperBackground]; [self setupDock]; [self setupLiveAppView]; @@ -99,17 +104,25 @@ - (id)initWithBundleIdentifier:(id)identifier objcInvoke(appSceneView, @"drawCarplayPlaceholder"); } } + + // Add observer the user changing the Dock's Alignment via preferences + id observer = [[objc_getClass("NSDistributedNotificationCenter") defaultCenter] addObserverForName:PREFERENCES_CHANGED_NOTIFICATION object:kPrefsDockAlignmentChanged queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) { + // Update this processes' preference cache + [[CRPreferences sharedInstance] reloadPreferences]; + // Redraw the dock + [self setupDock]; + // Relayout the app view + [self resizeAppViewForOrientation:_orientation fullscreen:_isFullscreen forceUpdate:YES]; + }]; + [_observers addObject:observer]; } return self; } -- (void)setupDock +- (void)setupWallpaperBackground { CGRect rootWindowFrame = [[self rootWindow] frame]; - UITraitCollection *carplayTrait = [UITraitCollection traitCollectionWithUserInterfaceIdiom:3]; - UITraitCollection *interfaceStyleTrait = [UITraitCollection traitCollectionWithUserInterfaceStyle:1]; - UITraitCollection *traitCollection = [UITraitCollection traitCollectionWithTraitsFromCollections:@[carplayTrait, interfaceStyleTrait]]; UIImageView *wallpaperImageView = [[UIImageView alloc] initWithFrame:rootWindowFrame]; id defaultWallpaper = objcInvoke(objc_getClass("CRSUIWallpaperPreferences"), @"defaultWallpaper"); @@ -121,8 +134,21 @@ - (void)setupDock [wallpaperBlurView setFrame:rootWindowFrame]; [wallpaperImageView addSubview:wallpaperBlurView]; [[self rootWindow] addSubview:wallpaperImageView]; +} + +- (void)setupDock +{ + // If the dock already exists, remove it. This allows the dock to be redrawn easily if the user switches the alignment + if (_dockView) + { + [_dockView removeFromSuperview]; + } - self.dockView = [[UIView alloc] initWithFrame:CGRectMake(0, rootWindowFrame.origin.y, CARPLAY_DOCK_WIDTH, rootWindowFrame.size.height)]; + CGRect rootWindowFrame = [[self rootWindow] frame]; + BOOL rightHandDock = [[CRPreferences sharedInstance] dockAlignment] == CRDockAlignmentRight; + + CGFloat dockXOrigin = (rightHandDock) ? rootWindowFrame.size.width - CARPLAY_DOCK_WIDTH : 0; + self.dockView = [[UIView alloc] initWithFrame:CGRectMake(dockXOrigin, rootWindowFrame.origin.y, CARPLAY_DOCK_WIDTH, rootWindowFrame.size.height)]; // Setup dock visual effects id blurEffect = objcInvoke_1(objc_getClass("UIBlurEffect"), @"effectWithBlurRadius:", 20.0); @@ -137,6 +163,9 @@ - (void)setupDock [[self rootWindow] addSubview:self.dockView]; NSBundle *carplayBundle = [NSBundle bundleWithPath:@"/System/Library/CoreServices/CarPlay.app"]; + UITraitCollection *carplayTrait = [UITraitCollection traitCollectionWithUserInterfaceIdiom:3]; + UITraitCollection *interfaceStyleTrait = [UITraitCollection traitCollectionWithUserInterfaceStyle:1]; + UITraitCollection *traitCollection = [UITraitCollection traitCollectionWithTraitsFromCollections:@[carplayTrait, interfaceStyleTrait]]; CGFloat buttonSize = 35; UIButton *closeButton = [UIButton buttonWithType:UIButtonTypeCustom]; @@ -372,6 +401,12 @@ - (void)dismiss // Invalidate the scene monitor [self.sceneMonitor invalidate]; + // Remove any observers + for (id observer in _observers) + { + [[objc_getClass("NSDistributedNotificationCenter") defaultCenter] removeObserver:observer]; + } + void (^cleanupAfterCarplay)() = ^() { // Notify the application process to stop enforcing an orientation lock int resetOrientationLock = -1; @@ -519,9 +554,10 @@ - (void)resizeAppViewForOrientation:(int)desiredOrientation fullscreen:(BOOL)ful [hostingContentView setTransform:CGAffineTransformMakeScale(widthScale, heightScale)]; [[self.appViewController view] setFrame:CGRectMake(xOrigin, [[self.appViewController view] frame].origin.y, carplayDisplaySize.width, carplayDisplaySize.height)]; + BOOL rightHandDock = [[CRPreferences sharedInstance] dockAlignment] == CRDockAlignmentRight; UIView *containingView = [self appContainerView]; CGRect containingViewFrame = [containingView frame]; - containingViewFrame.origin.x = dockWidth; + containingViewFrame.origin.x = (rightHandDock) ? 0 : dockWidth; [containingView setFrame:containingViewFrame]; [self.dockView setAlpha: (fullscreen) ? 0: 1]; diff --git a/src/CRPreferences.h b/src/CRPreferences.h new file mode 100644 index 0000000..88a81f8 --- /dev/null +++ b/src/CRPreferences.h @@ -0,0 +1,22 @@ +#include + +typedef NS_ENUM(NSInteger, CRDockAlignment) { + CRDockAlignmentLeft = 0, + CRDockAlignmentRight, + CRDockAlignmentAuto, +}; + +@interface CRPreferences : NSObject + +@property (nonatomic, retain) NSDictionary *cachedPreferences; + ++ (instancetype)sharedInstance; + +- (void)reloadPreferences; +- (void)writePreferences; +- (void)updateValue:(id)value forPreferenceKey:(NSString *)key; + +- (NSArray *)excludedApplications; +- (CRDockAlignment)dockAlignment; + +@end \ No newline at end of file diff --git a/src/CRPreferences.mm b/src/CRPreferences.mm new file mode 100644 index 0000000..d9ff4ca --- /dev/null +++ b/src/CRPreferences.mm @@ -0,0 +1,69 @@ +#include "common.h" + +@implementation CRPreferences + ++ (instancetype)sharedInstance +{ + static CRPreferences *sharedInstance = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + sharedInstance = [[CRPreferences alloc] init]; + }); + return sharedInstance; +} + +- (id)init +{ + if ((self = [super init])) + { + [self reloadPreferences]; + } + + return self; +} + +- (void)reloadPreferences +{ + if ([[NSFileManager defaultManager] fileExistsAtPath:PREFERENCES_PLIST_PATH]) + { + _cachedPreferences = [[NSDictionary alloc] initWithContentsOfFile:PREFERENCES_PLIST_PATH]; + } + else { + _cachedPreferences = @{}; + } +} + +- (void)writePreferences +{ + [_cachedPreferences writeToFile:PREFERENCES_PLIST_PATH atomically:NO]; +} + +- (void)updateValue:(id)value forPreferenceKey:(NSString *)key +{ + NSMutableDictionary *copy = [_cachedPreferences mutableCopy]; + copy[key] = value; + _cachedPreferences = copy; + [self writePreferences]; +} + +- (NSArray *)excludedApplications +{ + if (_cachedPreferences && [_cachedPreferences valueForKey:@"excludedApps"]) + { + return [_cachedPreferences valueForKey:@"excludedApps"]; + } + + return @[]; +} + +- (CRDockAlignment)dockAlignment +{ + if (_cachedPreferences && [_cachedPreferences valueForKey:@"dockAlignment"]) + { + return (CRDockAlignment)[[_cachedPreferences valueForKey:@"dockAlignment"] intValue]; + } + + return CRDockAlignmentAuto; +} + +@end \ No newline at end of file diff --git a/src/common.h b/src/common.h index d687d05..01672c8 100644 --- a/src/common.h +++ b/src/common.h @@ -2,6 +2,7 @@ #include #include #include +#include "CRPreferences.h" #define BAIL_IF_UNSUPPORTED_IOS { \ if ([[[UIDevice currentDevice] systemVersion] compare:@"14.0" options:NSNumericSearch] == NSOrderedAscending) \ @@ -42,6 +43,7 @@ static char *kPropertyKey_didDrawPlaceholder; #define kPrefsAppDataRequesting @"Requesting" #define kPrefsAppDataReceiving @"Receiving" #define kPrefsAppLibraryChanged @"appLibrary" +#define kPrefsDockAlignmentChanged @"dockAlignment" #define BLACKLIST_PLIST_PATH @"/var/mobile/Library/Preferences/com.carplayenable.blacklisted-apps.plist" diff --git a/src/hooks/CarPlay.xm b/src/hooks/CarPlay.xm index aa2173e..a6c1f56 100644 --- a/src/hooks/CarPlay.xm +++ b/src/hooks/CarPlay.xm @@ -1,5 +1,5 @@ -#include "../common.h" #include "../crash_reporting/reporting.h" +#include "../common.h" /* Injected into the CarPlay process @@ -28,14 +28,8 @@ void addCarplayDeclarationsToAppLibrary(id appLibrary) } // Load exluded apps from user's preferences - if ([[NSFileManager defaultManager] fileExistsAtPath:PREFERENCES_PLIST_PATH]) - { - NSDictionary *prefs = [[NSDictionary alloc] initWithContentsOfFile:PREFERENCES_PLIST_PATH]; - if ([prefs valueForKey:@"excludedApps"]) - { - blacklistedIdentifiers = [blacklistedIdentifiers arrayByAddingObjectsFromArray:[prefs valueForKey:@"excludedApps"]]; - } - } + NSArray *userExcludedApps = [[CRPreferences sharedInstance] excludedApplications]; + blacklistedIdentifiers = [blacklistedIdentifiers arrayByAddingObjectsFromArray:userExcludedApps]; for (id appInfo in objcInvoke(appLibrary, @"allInstalledApplications")) { @@ -232,7 +226,7 @@ Used for adding "carplay declaration" to newly installed apps so they appear on id _self = %orig; // Register for Preference Changed notifications [[objc_getClass("NSDistributedNotificationCenter") defaultCenter] addObserverForName:PREFERENCES_CHANGED_NOTIFICATION object:kPrefsAppLibraryChanged queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) { - + [[CRPreferences sharedInstance] reloadPreferences]; // Reload app library - in case apps were added/removed id updatedLibrary = objcInvoke(objc_getClass("CARApplication"), @"_newApplicationLibrary"); objcInvoke_1(self, @"setLibrary:", updatedLibrary); diff --git a/src/hooks/SpringBoard.xm b/src/hooks/SpringBoard.xm index 1fbd089..b6d1970 100644 --- a/src/hooks/SpringBoard.xm +++ b/src/hooks/SpringBoard.xm @@ -455,14 +455,5 @@ int hook_BKSDisplayServicesSetScreenBlanked(int arg1) // Hook BKSDisplayServicesSetScreenBlanked() - necessary for allowing animations/video when the screen is off void *_BKSDisplayServicesSetScreenBlanked = dlsym(dlopen(NULL, 0), "BKSDisplayServicesSetScreenBlanked"); MSHookFunction(_BKSDisplayServicesSetScreenBlanked, (void *)hook_BKSDisplayServicesSetScreenBlanked, (void **)&orig_BKSDisplayServicesSetScreenBlanked); - - // Write default preference values if needed - if (![[NSFileManager defaultManager] fileExistsAtPath:PREFERENCES_PLIST_PATH]) - { - NSDictionary *preferences = @{ - @"excludedApps": @[], - }; - [preferences writeToFile:PREFERENCES_PLIST_PATH atomically:NO]; - } } } \ No newline at end of file