Skip to content

Commit

Permalink
Initial Commit
Browse files Browse the repository at this point in the history
Added version 1.0 of BTManager+ to git
  • Loading branch information
BeckettOBrien committed Aug 8, 2020
0 parents commit 3a85e27
Show file tree
Hide file tree
Showing 30 changed files with 2,042 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.theos/
packages/
.vscode/
*.temp
1 change: 1 addition & 0 deletions BTManager+.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{ Filter = { Bundles = ( "com.apple.springboard" ); }; }
92 changes: 92 additions & 0 deletions BTManager.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
@class NSString;

@class NSMutableArray;

@interface SBBluetoothController : NSObject {

NSMutableArray* _devices;
BOOL _tetheringConnected;

}
+(id)sharedInstance;
-(void)startWatchingForDevices;
-(BOOL)tetheringConnected;
-(void)stopWatchingForDevices;
-(id)firstBTDeviceToReportBatteryLevel;
-(void)iapDeviceChanged:(id)arg1 ;
-(void)connectionChanged:(id)arg1 ;
-(void)addDeviceNotification:(id)arg1 ;
-(void)removeDeviceNotification:(id)arg1 ;
-(void)batteryChanged:(id)arg1 ;
-(void)bluetoothDeviceInitiatedVoiceControl:(id)arg1 ;
-(void)bluetoothDeviceEndedVoiceControl:(id)arg1 ;
-(void)noteDevicesChanged;
-(void)updateTetheringConnected;
-(void)updateBattery;
-(BOOL)canReportBatteryLevel;
-(id)deviceForAudioRoute:(id)arg1 ;
-(void)dealloc;
-(int)batteryLevel;
@end

@interface BluetoothDevice : NSObject
-(id)name;
-(id)address;
@end

@interface BTSDevice : NSObject
-(id)name;
-(id)identifier;
@end

@interface BTSDeviceClassic : BTSDevice
@end

@interface SBApplication: NSObject
@property (nonatomic,readonly) NSString * bundleIdentifier;
@property (nonatomic,readonly) NSString * displayName;
@end

@interface MPAVRoute : NSObject
@property (nonatomic,readonly) NSString * routeUID;
-(id)routeName;
-(BOOL)isPickedOnPairedDevice;
-(BOOL)isSplitterCapable;
-(BOOL)isShareableRoute;
-(BOOL)isAirpodsRoute;
-(BOOL)isSplitRoute;
@end

@interface MPAVRoutingController : NSObject
@property (nonatomic, readonly, copy) NSArray *availableRoutes;
@property (nonatomic, readonly) MPAVRoute* pickedRoute;
-(bool)pickRoute:(id)arg1;
-(id)availableRoutes;
@end

@interface SBMediaController : NSObject {
MPAVRoutingController* _routingController;
}
@property (nonatomic,assign,readonly) SBApplication * nowPlayingApplication;
+(id)sharedInstance;
-(void)routingControllerAvailableRoutesDidChange:(id)arg1;
-(void)_mediaRemoteNowPlayingApplicationDidChange:(id)arg1;
-(BOOL)isPaused;
-(BOOL)isPlaying;
-(BOOL)togglePlayPauseForEventSource:(NSInteger)arg1;
@end

@interface MRAVConcreteOutputDevice : NSObject
-(id)name;
@end

@interface MPAVOutputDeviceRoute : MPAVRoute
-(bool)isAppleTVRoute;
-(id)routeUID;
// -(id)routeName;
// -(BOOL)isPickedOnPairedDevice;
@end

@interface MPAVRoutingViewControllerDelegate
-(void)routingViewController:(id)arg1 didPickRoute:(id)arg2;
@end
164 changes: 164 additions & 0 deletions BTManager.x
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
#import <BTManager.h>

BOOL enabled = NO;
NSDictionary* deviceOrder;
NSDictionary* deviceSettings;
SBMediaController* mediaController;
MPAVRoutingController* routeController;
NSMutableDictionary* routes;
MPAVRoute* activeRoute;
MPAVRoute* previousRoute;

SBApplication* nowPlayingApplication;

void updateAvailableRoutes() {
mediaController = [%c(SBMediaController) sharedInstance];
routeController = [mediaController valueForKey:@"_routingController"];
NSArray* availableRoutes = [routeController availableRoutes];
[routes removeAllObjects];
for (MPAVOutputDeviceRoute* route in availableRoutes) {
if ([route isAppleTVRoute]) {
continue;
}
NSString* uid = route.routeUID;
[routes setObject:route forKey:uid];
}
activeRoute = [routeController pickedRoute];
}

void updateActiveRoute() {
mediaController = [%c(SBMediaController) sharedInstance];
routeController = [mediaController valueForKey:@"_routingController"];
for (NSString* routeAddress in deviceOrder[nowPlayingApplication.bundleIdentifier] ?: deviceOrder[@"SYSTEM"]) {
for (NSString* routeUID in routes) {
if ([routeUID containsString:routeAddress]) {
activeRoute = routes[routeUID];
[routeController pickRoute:routes[routeUID]];
return;
}
}
}

for (NSString* routeUID in routes) {
if ([routeUID isEqual:@"Speaker"]) {
[routeController pickRoute:routes[routeUID]];
return;
}
}
}

static void loadPrefs() {
NSMutableDictionary *settings = [[NSMutableDictionary alloc] initWithContentsOfFile:@"/var/mobile/Library/Preferences/com.beckettobrien.btmanagerprefs.plist"];

enabled = [[settings objectForKey:@"enabled"] boolValue];
deviceOrder = [settings objectForKey:@"deviceOrder"] ?: [[NSDictionary alloc] init];
deviceSettings = [settings objectForKey:@"deviceSettings"] ?: [[NSDictionary alloc] init];
}

%hook SBBluetoothController

-(id)deviceForAudioRoute:(id)arg1 {
id out = %orig;
if (enabled == 0) {
return out;
}

if ([arg1[@"RouteUID"] isEqual:activeRoute.routeUID]) {
return out;
}

previousRoute = activeRoute;

updateAvailableRoutes();

NSString* mac;
for (NSString* routeAddress in deviceSettings) {
if ([arg1[@"RouteUID"] containsString:routeAddress]) {
mac = routeAddress;
}
}
if (!mac) {
return out;
}
if ([deviceSettings[mac][@"switchConfirm"] isEqual:@0]) {
return out;
}

if ([[%c(SBMediaController) sharedInstance] isPlaying]) {
[[%c(SBMediaController) sharedInstance] togglePlayPauseForEventSource:0];
}

UIAlertController *alert = [UIAlertController alertControllerWithTitle:[NSString stringWithFormat:@"Switch to %@", arg1[@"RouteName"]] message:
@"Confirm to switch to this device, cancel to return to original device if available or switch to speakers and stop."
preferredStyle:UIAlertControllerStyleAlert];

UIAlertAction *ok = [UIAlertAction actionWithTitle:@"Confirm" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
if ([[%c(SBMediaController) sharedInstance] isPaused]) {
[[%c(SBMediaController) sharedInstance] togglePlayPauseForEventSource:0];
}
}];
UIAlertAction *cancel = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
updateAvailableRoutes();
MPAVOutputDeviceRoute* route = routes[previousRoute.routeUID] ?: routes[@"Speaker"];
[[[%c(SBMediaController) sharedInstance] valueForKey:@"_routingController"] pickRoute:route];
if (![route.routeUID isEqual:@"Speaker"]) {
[[%c(SBMediaController) sharedInstance] togglePlayPauseForEventSource:0];
}
}];
[alert addAction:cancel];
[alert addAction:ok];

UIWindow* foundWindow = nil;
for (UIWindow* window in [[UIApplication sharedApplication] windows]) {
if (window.isKeyWindow) {
foundWindow = window;
break;
}
}
[foundWindow.rootViewController presentViewController:alert animated:YES completion:nil];

return out;
}

%end

%hook SBMediaController
-(void)routingControllerAvailableRoutesDidChange:(id)arg1 {
%orig;

if (enabled == 0) {
return;
}

updateAvailableRoutes();
if (!routes[activeRoute.routeUID]) {
updateActiveRoute();
}
}
-(void)_mediaRemoteNowPlayingApplicationDidChange:(id)arg1 {
%orig;
if (enabled == 0) {
return;
}

nowPlayingApplication = [self nowPlayingApplication];
updateAvailableRoutes();
updateActiveRoute();
}

%end

%hook MPAVRoutingController

-(bool)pickRoute:(id)arg1 {
updateAvailableRoutes();
return %orig;
}

%end

%ctor {
routes = [NSMutableDictionary new];
loadPrefs();
CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), NULL, (CFNotificationCallback)loadPrefs, CFSTR("com.beckettobrien.btmanagerprefs.settingschanged"), NULL, CFNotificationSuspensionBehaviorCoalesce);
}
Loading

0 comments on commit 3a85e27

Please sign in to comment.