-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathTweak.xm
executable file
·265 lines (225 loc) · 10.4 KB
/
Tweak.xm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
#import "BSLaunchdUtilities.h"
#import "MediaRemote.h"
#import "SpringBoard.h"
#import <notify.h>
#define kBKSBackgroundModeContinuous @"continuous"
#define singleNowPlaying @"com.apple.Music"
static NSString * const kNowPlayingAppNotificationName = @"SBMediaNowPlayingAppChangedNotification";
static NSString * const kNowPlayingNotificationName = @"SBMediaNowPlayingChangedNotification";
static NSString * const kNowPlayingBundleIDKey = @"nowPlayingBundleID";
static NSString * const kNowPlayingPIDKey = @"nowPlayingPID";
static NSString * const kStatePlistPath = @"/User/Library/Preferences/com.jblounge.donotstop.plist";
static const unsigned kApplicationStateForegroundRunning = 4;
static NSString *currentNowPlayingBundleID = @"com.apple.Music";
static int currentNowPlayingPID = 0;
static BOOL currentNowPlayingValid = NO;
//Load the now current playing for respring
static void loadCurrentNowPlaying() {
NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:kStatePlistPath];
currentNowPlayingBundleID = [dict[kNowPlayingBundleIDKey] retain];
currentNowPlayingPID = [dict[kNowPlayingPIDKey] intValue];
currentNowPlayingValid = NO; // Invalid until verified in -|_serviceClientAddedWithProcessHandle|
}
//Update the dictionary with the now playing tweak if ness
static void updateAndSaveCurrentNowPlaying(NSString *bundleID, int pid) {
if ([currentNowPlayingBundleID isEqualToString:bundleID] && currentNowPlayingPID == pid) {
return;
}
if ([bundleID isEqualToString:singleNowPlaying] || bundleID == nil || pid == 0){
[currentNowPlayingBundleID release];
currentNowPlayingBundleID = [bundleID copy];
currentNowPlayingPID = pid;
currentNowPlayingValid = (bundleID && pid > 0);
NSDictionary *dict = !currentNowPlayingValid ? @{} : @{
kNowPlayingBundleIDKey: bundleID,
kNowPlayingPIDKey: @(pid)
};
[dict writeToFile:kStatePlistPath atomically:YES];
}
}
//Send notifications for other tweaks to notice we are rocking and a rolling.
static void postMediaRemoteNotification() {
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center postNotificationName:(NSString *)kMRMediaRemoteNowPlayingApplicationDidChangeNotification
object:nil];
[center postNotificationName:(NSString *)kMRMediaRemoteNowPlayingApplicationIsPlayingDidChangeNotification
object:nil
userInfo:@{(NSString *)kMRMediaRemoteNowPlayingApplicationIsPlayingUserInfoKey: @(YES)}];
}
// Called by +|FBApplicationProcess deleteAllJobs|. Maybe hook that instead?
//Prevents launchd from shutting the apps down.
%hook BSLaunchdUtilities
+ (void)deleteAllJobsWithLabelPrefix:(NSString *)prefix {
if (!currentNowPlayingBundleID) {
%orig;
return;
}
NSArray<NSString *> *allJobLabels = [self allJobLabels];
for (NSString *job in allJobLabels) {
if ([job hasPrefix:prefix]) {
if (![job containsString:currentNowPlayingBundleID]) {
[self deleteJobWithLabel:job];
} else {
// Make sure it's the right PID.
int pid = [self pidForLabel:job];
if (pid != currentNowPlayingPID) {
[self deleteJobWithLabel:job];
}
}
}
}
}
%end
// Need to notify the app that we started back up.
%hook SpringBoard
- (void)applicationDidFinishLaunching:(id)application {
if (currentNowPlayingValid) {
[[%c(BKSProcessAssertion) alloc] initWithPID:currentNowPlayingPID flags:BKSProcessAssertionFlagPreventSuspend | BKSProcessAssertionFlagPreventThrottleDownCPU | BKSProcessAssertionFlagWantsForegroundResourcePriority reason:BKSProcessAssertionReasonContinuous name:kBKSBackgroundModeContinuous withHandler:nil];
}
%orig;
if (currentNowPlayingValid) {
[self dnstp_fixUpNowPlayingForPID:currentNowPlayingPID bundleID:singleNowPlaying];
}
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(dnstp_nowPlayingChanged:)
name:kNowPlayingNotificationName
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(dnstp_nowPlayingAppChanged:)
name:kNowPlayingAppNotificationName
object:nil];
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
%orig;
}
//Calls update function
%new
- (void)dnstp_nowPlayingAppChanged:(NSNotification *)notification {
SBMediaController *mediaController = notification.object;
[self dnstp_updateNowPlayingWithMediaController:mediaController];
}
//Calls update function
%new
- (void)dnstp_nowPlayingChanged:(NSNotification *)notification {
SBMediaController *mediaController = notification.object;
[self dnstp_updateNowPlayingWithMediaController:mediaController];
}
//Updates the now playing application (nil if nothing is playing)
%new
- (void)dnstp_updateNowPlayingWithMediaController:(SBMediaController *)mediaController {
SBApplication *nowPlayingApplication = mediaController.nowPlayingApplication;
NSString *bundleIdentifier = [nowPlayingApplication bundleIdentifier];
int pid = MSHookIvar<int>(mediaController, "_lastNowPlayingAppPID");
BOOL playing = [mediaController isPlaying];
if (!playing || pid <= 0 || !bundleIdentifier || ![bundleIdentifier isEqualToString:singleNowPlaying]) {
updateAndSaveCurrentNowPlaying(nil, 0);
} else {
updateAndSaveCurrentNowPlaying(bundleIdentifier, pid);
}
}
//This reattaches the process to Springboard
%new
- (void)dnstp_fixUpNowPlayingForPID:(int)pid bundleID:(NSString *)bundleID {
if ([bundleID isEqualToString:singleNowPlaying]){
FBProcessManager *processManager = [%c(FBProcessManager) sharedInstance];
id process = [processManager applicationProcessForPID:pid];
if (!process) {
return;
}
SBApplication *application = [[%c(SBApplicationController) sharedInstance] applicationWithBundleIdentifier:bundleID];
[application processWillLaunch:process];
[application processDidLaunch:process];
[application setApplicationState:kApplicationStateForegroundRunning];
[(SpringBoard *)[UIApplication sharedApplication] launchApplicationWithIdentifier:bundleID suspended:YES];
dispatch_time_t timedis = dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC);
dispatch_after(timedis, dispatch_get_main_queue(), ^(void){
SBApplication *application = [[%c(SBApplicationController) sharedInstance] applicationWithBundleIdentifier:bundleID];
FBScene *scene = [application mainScene];
if (!scene || !scene.settings || !scene.mutableSettings) {
return;
}
FBSMutableSceneSettings *sceneSettings = scene.mutableSettings;
sceneSettings.backgrounded = NO;
[scene _applyMutableSettings:sceneSettings withTransitionContext:nil completion:nil];
FBSMutableSceneSettings *sceneSettings2 = scene.mutableSettings;
sceneSettings2.backgrounded = YES;
[scene _applyMutableSettings:sceneSettings2 withTransitionContext:nil completion:nil];
});
notify_post("do-not-stop-the-party!");
postMediaRemoteNotification();
}
}
%end
// Fix for artwork not showing up immediately after a respring.
%hook MPULockScreenMediaControlsViewController
- (void)nowPlayingController:(id)npc nowPlayingInfoDidChange:(NSDictionary *)info {
id view = MSHookIvar<id>(self, "_mediaControlsView");
if (view) {
%orig;
}
}
%end
%group iOS10
%hook FBProcessManager
//Adds the process back into the process list for iOS 10
- (id)_serviceClientAddedWithProcessHandle:(FBSProcessHandle *)processHandle {
if (!currentNowPlayingBundleID) {
return %orig;
}
NSString *bundleIdentifier = processHandle.bundleIdentifier;
int pid = processHandle.pid;
long long type = processHandle.type;
// Type 2 seems to make the added process an FBApplicationProcess, not an FBProcess.
if ([bundleIdentifier isEqualToString:singleNowPlaying] && pid == currentNowPlayingPID) {
processHandle.type = 2;
id result = %orig;
processHandle.type = type;
currentNowPlayingValid = YES;
return result;
} else {
return %orig;
}
}
%end
%end
%group iOS9
//Adds the process back into the process list for iOS 9
%hook FBProcessManager
- (id)_serviceClientAddedWithPID:(int)pid isUIApp:(BOOL)isUIApp isExtension:(BOOL)isExte bundleID:(NSString *)bundleID {
if (!currentNowPlayingBundleID) {
return %orig;
}
// Type 2 seems to make the added process an FBApplicationProcess, not an FBProcess.
if ([bundleID isEqualToString:singleNowPlaying] && pid == currentNowPlayingPID) {
currentNowPlayingValid = YES;
}
return %orig;
}
%end
//Fixes an issue where the switcher would not remove the process from the Launchd
//This is a hack.... A better solution should be found.
%hook SBMainSwitcherViewController
-(void)_quitAppRepresentedByDisplayItem:(id)arg1 forReason:(long long)arg2 {
%orig;
if ([[arg1 valueForKey:@"_displayIdentifier"] isEqualToString:singleNowPlaying]){
NSArray<NSString *> *allJobLabels = [%c(BSLaunchdUtilities) allJobLabels];
for (NSString *job in allJobLabels) {
if ([job containsString:singleNowPlaying]) {
[%c(BSLaunchdUtilities) deleteJobWithLabel:job];
}
}
}
}
%end
%end
%ctor {
%init;
Class FBProcessManager = %c(FBProcessManager);
if ([FBProcessManager instancesRespondToSelector:@selector(_serviceClientAddedWithProcessHandle:)]) {
%init(iOS10);
} else if ([FBProcessManager instancesRespondToSelector:@selector(_serviceClientAddedWithPID:isUIApp:isExtension:bundleID:)]) {
%init(iOS9);
}
loadCurrentNowPlaying();
}