Skip to content

Commit

Permalink
Rootless Support (Fugu15 Max / Dopamine)
Browse files Browse the repository at this point in the history
Added support for rootless (Fugu15 Max / Dopamine)
Added audio routing selection view (WIP)
Minor refactoring and fixes for iOS 15
Minor updates to internal Audio Player
Known issues:
- Picking a new audio route does not always update the UI checkmark
  • Loading branch information
CreatureSurvive committed Apr 24, 2023
1 parent 0814310 commit a3672d9
Show file tree
Hide file tree
Showing 25 changed files with 865 additions and 85 deletions.
125 changes: 125 additions & 0 deletions Classes/AVRouting.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
//
// AVRouting.h
// Tranquil
//
// Created by Dana Buehre on 4/8/22.
//

#import <UIKit/UIKit.h>

@interface MRAVOutputDevice : NSObject
@end

@interface SFShareAudioViewController : UINavigationController
@property (nonatomic, copy) void (^completion)(void);
+ (SFShareAudioViewController *)instantiateViewController;
@end

@interface MPAVRoute : NSObject
@property (nonatomic,copy) NSString * routeName;
@property (assign,getter=isPicked,nonatomic) BOOL picked;
@property (nonatomic,readonly) NSString * routeUID;
@property (nonatomic, readonly, getter=isShareableRoute) BOOL shareableRoute;
@end

@interface MPAVEndpointRoute : MPAVRoute
+ (void)getActiveEndpointRouteWithCompletion:(id)completion ;
@end

@interface MPAVRoutingViewItem : NSObject
@property (nonatomic, readonly) NSInteger type;
@property (nonatomic, readonly) NSArray *routes;
@property (nonatomic, readonly) MPAVRoute *mainRoute;
@property (nonatomic, readonly) MPAVRoute *route;
- (MPAVRoute *)mainRoute;
@end

@interface MPAVRoutingController : NSObject
- (BOOL)pickRoute:(MPAVRoute *)route;
+ (void)getActiveRouteWithCompletion:(void (^)(MPAVRoute *route))completion;
+ (void)setActiveRoute:(MPAVRoute *)route completion:(id)completion;
@end

@protocol MPAVRoutingTableViewCellDelegate <NSObject>

@optional
-(void)routingCell:(id)arg1 mirroringSwitchValueDidChange:(BOOL)arg2;
-(void)routingCellDidTapToExpand:(id)arg1;
@end

@protocol MPAVRoutingViewControllerThemeDelegate <NSObject>

@optional
- (void)routingViewController:(id)routingViewController willDisplayCell:(id)cell;
- (void)routingViewController:(id)routingViewController willDisplayHeaderView:(id)header;
- (UIEdgeInsets*)contentInsetsForRoutingViewController:(id)routingViewController;
@end

@protocol MPAVRoutingViewControllerDelegate <NSObject>

@optional
- (void)routingViewController:(id)routingViewController didPickRoute:(MPAVRoute *)route;
- (void)routingViewControllerDidUpdateContents:(id)arg1;
- (void)routingViewController:(id)routingViewController didSelectRoutingViewItem:(MPAVRoutingViewItem *)routingViewItem;
@end

@protocol MPAVRoutingControllerDelegate <NSObject>

@optional
- (void)routingControllerAvailableRoutesDidChange:(id)arg1;
- (void)routingController:(MPAVRoutingController *)routingController pickedRouteDidChange:(MRAVOutputDevice *)outputDevice;
- (void)routingController:(MPAVRoutingController *)routingController didFailToPickRouteWithError:(id)error;
- (void)routingController:(MPAVRoutingController *)routingController pickedRoutesDidChange:(id)arg2;
- (void)routingControllerExternalScreenTypeDidChange:(id)arg1;
- (void)routingControllerDidPauseFromActiveRouteChange:(id)arg1;
@end

@interface MPAVClippingTableViewCell : UITableViewCell
- (void)_setShouldHaveFullLengthBottomSeparator:(BOOL)fullLength;
- (void)_setShouldHaveFullLengthTopSeparator:(BOOL)fullLength;
@end

@interface MPAVRoutingTableViewCell : MPAVClippingTableViewCell

@property (nonatomic, assign, getter=isPendingSelection) BOOL pendingSelection;
@property (nonatomic, assign) BOOL isDisplayedAsPicked;

- (UIView *)separatorView;
- (UILabel *)subtitleView;
- (UILabel *)titleView;
@end

@interface MPAVRoutingViewController : UIViewController {

NSArray* _cachedPendingPickedRoutes;
}
@property (nonatomic, readonly) MPAVRoutingController *_routingController;
@property (nonatomic, weak) id<MPAVRoutingViewControllerDelegate> delegate;
@property (nonatomic, weak) id<MPAVRoutingViewControllerThemeDelegate> themeDelegate;
@property (nonatomic, copy) NSNumber * discoveryModeOverride;
@property (nonatomic, assign) NSUInteger mirroringStyle;
@property (nonatomic, assign) NSUInteger iconStyle;
@property (nonatomic, readonly) NSUInteger style;

@property (assign,setter=_setShouldAutomaticallyUpdateRoutesList:,nonatomic) BOOL _shouldAutomaticallyUpdateRoutesList;
@property (nonatomic, assign, setter=_setShouldPickRouteOnSelection:) BOOL _shouldPickRouteOnSelection;

@property (nonatomic,retain) MPAVEndpointRoute * endpointRoute;

@property (nonatomic, readonly) UITableView *_tableView;

- (instancetype)initWithStyle:(NSUInteger)style;

- (void)enqueueRefreshUpdate;
- (void)_setupUpdateTimerIfNecessary;
- (void)_beginRouteDiscovery;
- (void)_endRouteDiscovery;
- (void)_updateDisplayedRoutes;
- (void)_setNeedsDisplayedRoutesUpdate;
- (void)resetDisplayedRoutes;

- (void)routingController:(MPAVRoutingController *)routingController didFailToPickRouteWithError:(NSError *)error;
- (void)routingController:(MPAVRoutingController *)routingController pickedRoutesDidChange:(NSArray <MRAVOutputDevice *> *)outputDevices;
- (void)_configureCell:(MPAVRoutingTableViewCell *)cell forIndexPath:(NSIndexPath *)indexPath;

@end
50 changes: 47 additions & 3 deletions Classes/Haptic.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,24 +12,68 @@
#import <UIKit/UINotificationFeedbackGenerator.h>


NS_INLINE void PlayImpact(UIImpactFeedbackStyle feedbackStyle)
NS_INLINE void HapticImpact(UIImpactFeedbackStyle feedbackStyle)
{
UIImpactFeedbackGenerator *feedbackGenerator = [[UIImpactFeedbackGenerator alloc] initWithStyle:feedbackStyle];
[feedbackGenerator prepare];
[feedbackGenerator impactOccurred];
}

NS_INLINE void PlayImpactWithSound(UIImpactFeedbackStyle feedbackStyle, SystemSoundID soundID)
NS_INLINE void HapticImpactAfterDelay(UIImpactFeedbackStyle feedbackStyle, NSTimeInterval delay)
{
UIImpactFeedbackGenerator *feedbackGenerator = [[UIImpactFeedbackGenerator alloc] initWithStyle:feedbackStyle];
[feedbackGenerator prepare];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (uint64_t)(delay * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[feedbackGenerator impactOccurred];
});
}

NS_INLINE void HapticImpactWithSound(UIImpactFeedbackStyle feedbackStyle, SystemSoundID soundID)
{
UIImpactFeedbackGenerator *feedbackGenerator = [[UIImpactFeedbackGenerator alloc] initWithStyle:feedbackStyle];
[feedbackGenerator prepare];
AudioServicesPlaySystemSound(soundID);
[feedbackGenerator impactOccurred];
}

NS_INLINE void PlayNotificationWithSound(UINotificationFeedbackType type)
NS_INLINE void HapticImpactWithSoundAfterDelay(UIImpactFeedbackStyle feedbackStyle, SystemSoundID soundID, NSTimeInterval delay)
{
UIImpactFeedbackGenerator *feedbackGenerator = [[UIImpactFeedbackGenerator alloc] initWithStyle:feedbackStyle];
[feedbackGenerator prepare];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (uint64_t)(delay * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
AudioServicesPlaySystemSound(soundID);
[feedbackGenerator impactOccurred];
});
}

NS_INLINE void HapticSelection()
{
UISelectionFeedbackGenerator *feedbackGenerator = [[UISelectionFeedbackGenerator alloc] init];
[feedbackGenerator prepare];
[feedbackGenerator selectionChanged];
}

NS_INLINE void HapticSelectionAfterDelay(NSTimeInterval delay)
{
UISelectionFeedbackGenerator *feedbackGenerator = [[UISelectionFeedbackGenerator alloc] init];
[feedbackGenerator prepare];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (uint64_t)(delay * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[feedbackGenerator selectionChanged];
});
}

NS_INLINE void HapticNotification(UINotificationFeedbackType type)
{
UINotificationFeedbackGenerator *feedbackGenerator = [[UINotificationFeedbackGenerator alloc] init];
[feedbackGenerator prepare];
[feedbackGenerator notificationOccurred:type];
}

NS_INLINE void HapticNotificationAfterDelay(UINotificationFeedbackType type, NSTimeInterval delay)
{
UINotificationFeedbackGenerator *feedbackGenerator = [[UINotificationFeedbackGenerator alloc] init];
[feedbackGenerator prepare];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (uint64_t)(delay * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[feedbackGenerator notificationOccurred:type];
});
}
16 changes: 15 additions & 1 deletion Classes/Material.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,14 @@
+ (MTMaterialView *)materialViewWithRecipe:(NSInteger)recipe options:(NSUInteger)options;
@end

@interface MTVisualStylingProvider : NSObject
+ (Class)_visualStylingClass;
+ (id)_visualStylingProviderForStyleSetNamed:(NSString *)styleSetName inBundle:(NSBundle *)bundle;
+ (id)_visualStylingProviderForRecipe:(NSInteger)recipe andCategory:(NSInteger)category;
+ (id)_visualStylingProviderForRecipeNamed:(NSString *)recipeName andCategory:(NSInteger)category;
+ (id)_visualStylingProviderForRecipe:(NSInteger)recipe category:(NSInteger)category andUserInterfaceStyle:(NSInteger)arg3;
@end

NS_INLINE __unused MTMaterialView *ControlCenterMaterialWithConfiguration(NSInteger configuration, NSUInteger legacyOptions)
{
NSInteger controlCenterRecipe = 4;
Expand All @@ -39,4 +47,10 @@ NS_INLINE __unused MTMaterialView *ControlCenterForegroundMaterial()
NS_INLINE __unused MTMaterialView *ControlCenterVibrantLightMaterial()
{
return ControlCenterMaterialWithConfiguration(3, 32);
}
}

NS_INLINE __unused MTVisualStylingProvider *ControlCenterStylingProvider()
{
Class _MTVisualStylingProvider = NSClassFromString(@"MTVisualStylingProvider");
return [_MTVisualStylingProvider _visualStylingProviderForStyleSetNamed:@"moduleStyle" inBundle:[NSBundle bundleForClass:_MTVisualStylingProvider]];
};
16 changes: 14 additions & 2 deletions Classes/Prefix.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@

#import <QuartzCore/QuartzCore.h>
#import <Foundation/Foundation.h>
#import <os/log.h>
#import <dlfcn.h>
#import "rootless.h"

// https://stackoverflow.com/a/14770282/4668186
#define CLAMP(x, low, high) ({\
Expand All @@ -29,12 +31,22 @@
#define TranquilPreferencesChanged "com.creaturecoding.tranquil/preferences-changed"
#define TranquilPreferencesChangedExternal "com.creaturecoding.tranquil/preferences-changed-externally"

#define TranquilBundlePath @"/Library/ControlCenter/Bundles/Tranquil.bundle"
#define TranquilBundlePath ROOT_PATH_NS(@"/Library/ControlCenter/Bundles/Tranquil.bundle")
#define TranquilSupportPath @"/var/mobile/Library/Application Support/Tranquil/"
#define TranquilBundledAudioPath @"/Library/ControlCenter/Bundles/Tranquil.bundle/Audio"
#define TranquilBundledAudioPath ROOT_PATH_NS(@"/Library/ControlCenter/Bundles/Tranquil.bundle/Audio")
#define TranquilImportedAudioPath @"/var/mobile/Library/Application Support/Tranquil/Audio/"
#define TranquilDownloadableAudioPath @"/var/mobile/Library/Application Support/Tranquil/Downloadable/"

#define Log(format, ...) os_log(OS_LOG_DEFAULT, ("(com.creaturecoding.tranquil) %s [LOG Line %d] " format), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__);
#define Info(format, ...) os_log_info(OS_LOG_DEFAULT, ("(com.creaturecoding.tranquil) %s [INFO Line %d] " format), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__);
#define Error(format, ...) os_log_error(OS_LOG_DEFAULT, ("(com.creaturecoding.tranquil) %s [ERROR Line %d] " format), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__);
#define Fault(format, ...) os_log_fault(OS_LOG_DEFAULT, ("(com.creaturecoding.tranquil) %s [FAULT Line %d] " format), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__);
#define LogWithType(type, format, ...) os_log_with_type(OS_LOG_DEFAULT, type, ("(com.creaturecoding.tranquil) %s [Line %d] " format), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__);

@interface NSObject ()
- (id)safeValueForKey:(NSString *)key;
@end

NS_INLINE __unused NSBundle *ModuleBundle(BOOL loadIfNeeded)
{
static NSBundle *moduleBundle;
Expand Down
12 changes: 11 additions & 1 deletion Classes/TranquilListItemsController.m
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,17 @@ - (void)setSpinnerForCellAtIndexPath:(NSIndexPath *)indexPath enabled:(BOOL)enab

if (!cell.accessoryView || ![cell.accessoryView isKindOfClass:UIActivityIndicatorView.class]) {

UIActivityIndicatorView *spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
UIActivityIndicatorView *spinner;
if (@available(iOS 13.0, *)) {
spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleMedium];
} else {
// this is ugly, it allows compilation when targeting iOS 15 for rootless
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
#pragma clang diagnostic pop
}

[spinner setColor:UIColor.systemGrayColor];
[spinner setFrame:CGRectMake(0, 0, 24, 24)];
[cell setAccessoryView:spinner];
Expand Down
34 changes: 20 additions & 14 deletions Classes/TranquilMediaPlayer.m
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

#import "TranquilMediaPlayer.h"
#import "TranquilModule.h"
#import "Prefix.h"

#import <AVFoundation/AVFoundation.h>

Expand Down Expand Up @@ -41,7 +42,9 @@ + (TranquilMediaPlayer *)sharedInstance
dispatch_once(&once, ^{
sharedInstance = [TranquilMediaPlayer new];

[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback mode:AVAudioSessionModeDefault options:AVAudioSessionCategoryOptionMixWithOthers error:nil];
AVAudioSession *audioSession = [AVAudioSession sharedInstance];
[audioSession setCategory:AVAudioSessionCategoryPlayback mode:AVAudioSessionModeDefault options:AVAudioSessionCategoryOptionMixWithOthers error:nil];
[audioSession overrideOutputAudioPort:AVAudioSessionPortOverrideSpeaker error:nil];
[[NSClassFromString(@"SBMediaController") sharedInstance] addObserver:sharedInstance forKeyPath:NSStringFromSelector(@selector(nowPlayingProcessPID)) options:NSKeyValueObservingOptionNew context:NULL];
});

Expand Down Expand Up @@ -102,10 +105,14 @@ - (void)play:(NSString *)filePath volume:(float)volume withCompletion:(void(^_Nu
{
_volume = volume;

void (^complete)(BOOL) = ^(BOOL playing) {
if (completion) dispatch_async(dispatch_get_main_queue(), ^{ completion(playing); });
};

if (!filePath || ([self isPlaying] && [_currentlyPlayingFile isEqualToString:filePath])) {

_player.volume = _volume;
if (completion) completion([self isPlaying]);
complete([self isPlaying]);
return;
}

Expand All @@ -123,24 +130,23 @@ - (void)play:(NSString *)filePath volume:(float)volume withCompletion:(void(^_Nu

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{

self->_player = [[AVAudioPlayer alloc] initWithContentsOfURL:fileURL error:nil];
[self->_player setNumberOfLoops:-1];
[self->_player setVolume:_volume];
AVAudioPlayer *player = [[AVAudioPlayer alloc] initWithContentsOfURL:fileURL error:nil];
[player setNumberOfLoops:-1];
[player setVolume:self->_volume];

[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback mode:AVAudioSessionModeDefault options:AVAudioSessionCategoryOptionMixWithOthers error:nil];
[[AVAudioSession sharedInstance] setActive:YES withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation error:nil];
self->_player = player;

[self->_player prepareToPlay];
AVAudioSession *audioSession = [AVAudioSession sharedInstance];
[audioSession setCategory:AVAudioSessionCategoryPlayback mode:AVAudioSessionModeDefault options:AVAudioSessionCategoryOptionMixWithOthers error:nil];
[audioSession overrideOutputAudioPort:AVAudioSessionPortOverrideSpeaker error:nil];
[audioSession setActive:YES error:nil];

dispatch_async(dispatch_get_main_queue(), ^{
[self->_player play];
if (completion) completion([self isPlaying]);
});
complete([player play]);
});

} else if (completion) {

completion([self isPlaying]);
complete([self isPlaying]);
}
}

Expand Down Expand Up @@ -185,7 +191,7 @@ - (void)pauseForSample
}

// due to limitations of AVAudioSession categories, we can either offer mixed audio, or receive interruption notifications
// but not both, due to these limitations, we have to watch for now playing changes through private API inorder to replicate
// but not both, due to these limitations, we have to watch for now playing changes through private API in order to replicate
// iOS 15 background sounds features, specifically changing the volume when other media is playing. There are various ways
// of accomplishing this, but all of which have other limitations they introduce such as respecting the physical silent switch
// witch is undesirable for our purposes. so here we observe SBMediaController to check for now playing state changes.
Expand Down
Loading

0 comments on commit a3672d9

Please sign in to comment.