-
Notifications
You must be signed in to change notification settings - Fork 33
/
Copy pathPreferenceOrganizer2.xm
executable file
·751 lines (684 loc) · 34.8 KB
/
PreferenceOrganizer2.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
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
#import "PreferenceOrganizer2.h"
@interface PrefsListController : PSListController
@end
@interface PSUIPrefsListController : PSListController
@end
// Static specifier-overriding arrays (used when populating PSListController/etc)
static NSMutableArray *AppleAppSpecifiers, *SocialAppSpecifiers, *TweakSpecifiers, *AppStoreAppSpecifiers;
// Sneaky implementations of vanilla PSListControllers with the proper hidden specifiers
@implementation AppleAppSpecifiersController
-(NSArray *) specifiers {
if (!_specifiers) {
self.specifiers = AppleAppSpecifiers;
}
return _specifiers;
}
@end
@implementation SocialAppSpecifiersController
-(NSArray *) specifiers {
if (!_specifiers) {
self.specifiers = SocialAppSpecifiers;
}
return _specifiers;
}
@end
@implementation TweakSpecifiersController
-(NSArray *) specifiers {
if (!_specifiers) {
self.specifiers = TweakSpecifiers;
}
return _specifiers;
}
@end
@implementation AppStoreAppSpecifiersController
-(NSArray *) specifiers {
if (!_specifiers) {
self.specifiers = AppStoreAppSpecifiers;
}
return _specifiers;
}
@end
static BOOL shouldShowAppleApps;
static BOOL shouldShowTweaks;
static BOOL shouldShowAppStoreApps;
static BOOL shouldShowSocialApps;
static BOOL shouldSyslogSpam;
static BOOL ddiIsMounted = 0;
static BOOL deviceShowsTVProviders = 0;
static NSString *appleAppsLabel;
static NSString *socialAppsLabel;
static NSString *tweaksLabel;
static NSString *appStoreAppsLabel;
KarenLocalizer *karenLocalizer;
static NSMutableArray *unorganisedSpecifiers = nil;
static void PO2InitPrefs() {
PO2SyncPrefs();
PO2BoolPref(shouldSyslogSpam, syslogSpam, 0);
PO2BoolPref(shouldShowAppleApps, ShowAppleApps, 1);
PO2BoolPref(shouldShowTweaks, ShowTweaks, 1);
PO2BoolPref(shouldShowAppStoreApps, ShowAppStoreApps, 1);
if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_11_0) {
shouldShowSocialApps = 0;
} else {
PO2BoolPref(shouldShowSocialApps, ShowSocialApps, 1);
}
karenLocalizer = [[KarenLocalizer alloc] initWithKarenLocalizerBundle:@"PreferenceOrganizer2"];
PO2StringPref(appleAppsLabel, AppleAppsName, [karenLocalizer karenLocalizeString:@"APPLE_APPS"]);
PO2StringPref(socialAppsLabel, SocialAppsName, [karenLocalizer karenLocalizeString:@"SOCIAL_APPS"]);
PO2StringPref(tweaksLabel, TweaksName, [karenLocalizer karenLocalizeString:@"TWEAKS"]);
PO2StringPref(appStoreAppsLabel, AppStoreAppsName, [karenLocalizer karenLocalizeString:@"APP_STORE_APPS"]);
}
void removeOldAppleThirdPartySpecifiers(NSMutableArray <PSSpecifier *> *specifiers) {
NSMutableArray *itemsToDelete = [NSMutableArray array];
for (PSSpecifier *spec in specifiers) {
NSString *Id = spec.identifier;
if ([Id isEqualToString:@"com.apple.news"] || [Id isEqualToString:@"com.apple.iBooks"] || [Id isEqualToString:@"com.apple.podcasts"] || [Id isEqualToString:@"com.apple.itunesu"]) {
[itemsToDelete addObject:spec];
}
}
[specifiers removeObjectsInArray:itemsToDelete];
}
void fixupThirdPartySpecifiers(PSListController *self, NSArray <PSSpecifier *> *thirdParty, NSDictionary *appleThirdParty) {
// Then add all third party specifiers into correct categories
// Also remove them from the original locations
NSMutableArray *specifiers = [[NSMutableArray alloc] initWithArray:((PSListController *)self).specifiers];
if (shouldShowAppleApps) {
NSArray *appleThirdPartySpecifiers = [appleThirdParty allValues];
removeOldAppleThirdPartySpecifiers(AppleAppSpecifiers);
[AppleAppSpecifiers addObjectsFromArray:appleThirdPartySpecifiers];
[specifiers removeObjectsInArray:appleThirdPartySpecifiers];
}
if (shouldShowAppStoreApps) {
[AppStoreAppSpecifiers removeAllObjects];
[AppStoreAppSpecifiers addObjectsFromArray:thirdParty];
[specifiers removeObjectsInArray:thirdParty];
}
((PSListController *)self).specifiers = specifiers;
}
// For iOS 10
void removeOldAppleGroupSpecifiers(NSMutableArray <PSSpecifier *> *specifiers) {
NSMutableArray *itemsToDelete = [NSMutableArray array];
for (PSSpecifier *spec in specifiers) {
NSString *specID = spec.identifier;
if ([specID isEqualToString:@"APPLE_ACCOUNT_GROUP"] || [specID isEqualToString:@"ACCOUNTS_GROUP"] || [specID isEqualToString:@"MEDIA_GROUP"]) {
[itemsToDelete addObject:spec];
}
}
[specifiers removeObjectsInArray:itemsToDelete];
}
/*
## ####### ###### ######## ##
## ## ## ## ## ##
## ## ## ## ## ##
## ## ## ###### ## ########
## ## ## ## ## ##
## ## ## ## ## ## ##
## ####### ###### ## ##
*/
%group iOS7Up
%hook PrefsListController
// The ages-old iCloud prefpane crashing bug is caused by the fact that the following method sometimes may be invoked by -[(PSUI)PrefsListController appleAccountsDidChange] which calls for [self specifierForID:@"CASTLE"]... but it won't exist when PO2 "organises" it.
// So, we implement our own method that just does nothing if the iCloud specifier is nil
-(void) _setupiCloudSpecifier:(PSSpecifier *)specifier {
if (specifier == nil) {
return;
}
%orig(specifier);
}
-(void) _setupiCloudSpecifierAsync:(PSSpecifier *)specifier {
if (specifier == nil) {
return;
}
%orig(specifier);
}
-(void) _setupiCloudSpecifier:(PSSpecifier *)specifier withPrimaryAccount:(id)arg {
if (specifier == nil) {
return;
}
%orig(specifier, arg);
}
-(NSMutableArray *) specifiers {
NSMutableArray *specifiers = %orig();
PO2Log([NSString stringWithFormat:@"originalSpecifiers = %@", specifiers], shouldSyslogSpam);
if ((kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_11_0) && !(MSHookIvar<NSArray *>(self, "_thirdPartySpecifiers"))) {
return specifiers;
}
PO2InitPrefs();
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// Save the original, unorganised specifiers
if (unorganisedSpecifiers == nil) {
unorganisedSpecifiers = [specifiers.copy retain];
}
// Do a check for net.angelxwind.preferenceorganizer2
if (access(DPKG_PATH, F_OK) == -1) {
UIAlertView *aptAlert = [[UIAlertView alloc] initWithTitle:[karenLocalizer karenLocalizeString:@"WARNING"]
message:[NSString stringWithFormat:@"%@ %@ %@", [karenLocalizer karenLocalizeString:@"APT_DETAIL_1"], [karenLocalizer karenLocalizeString:@"APT_DETAIL_2"],[karenLocalizer karenLocalizeString:@"APT_DETAIL_3"]]
delegate:self
cancelButtonTitle:[karenLocalizer karenLocalizeString:@"OK"]
otherButtonTitles:nil];
[aptAlert show];
PO2Log([NSString stringWithFormat:@"%@", [karenLocalizer karenLocalizeString:@"APT_DETAIL_1"]], 1);
PO2Log([NSString stringWithFormat:@"%@", [karenLocalizer karenLocalizeString:@"APT_DETAIL_2"]], 1);
PO2Log([NSString stringWithFormat:@"%@", [karenLocalizer karenLocalizeString:@"APT_DETAIL_3"]], 1);
}
// Okay, let's start pushing paper.
int groupID = 0;
NSMutableDictionary *organizableSpecifiers = [[NSMutableDictionary alloc] init];
NSString *currentOrganizableGroup = nil;
// Loop that runs through all specifiers in the main Settings area. Once it cycles
// through all the specifiers for the pre-"Apple Apps" groups, starts filling the
// organizableSpecifiers array. This currently compares identifiers to prevent issues
// with extra groups (such as the single "Developer" group).
// CASTLE -> STORE -> ... -> DEVELOPER_SETTINGS -> ...
// NSLog(@"%@", specifiers);
for (int i = 0; i < specifiers.count; i++) { // We can't fast enumerate when order matters
PSSpecifier *s = (PSSpecifier *) specifiers[i];
NSString *identifier = s.identifier ?: @"";
// If we're not a group cell...
if (s->cellType != 0) {
// If we're hitting the Developer settings area, regardless of position, we need to steal
// its group specifier from the previous group and leave it out of everything.
if ([identifier isEqualToString:@"DEVELOPER_SETTINGS"]) {
NSMutableArray *lastSavedGroup = organizableSpecifiers[currentOrganizableGroup];
[lastSavedGroup removeObjectAtIndex:lastSavedGroup.count - 1];
// If DEVELOPER_SETTINGS is present, then that means the DDI must have been mounted.
ddiIsMounted = 1;
}
// If we're in the first item of the iCloud/Mail/Notes... group, setup the key string,
// grab the group from the previously enumerated specifier, and get ready to shift things into it.
else if ([identifier isEqualToString:(kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_11_0) ? @"STORE" : @"CASTLE"] ) {
currentOrganizableGroup = identifier;
NSMutableArray *newSavedGroup = [[NSMutableArray alloc] init];
[newSavedGroup addObject:specifiers[i - 1]];
[newSavedGroup addObject:s];
[organizableSpecifiers setObject:newSavedGroup forKey:currentOrganizableGroup];
}
// If we're in the first item of the iTunes/Music/Videos... group, setup the key string,
// steal the group from the previously organized specifier, and get ready to shift things into it.
else if ([identifier isEqualToString:@"STORE"]) {
currentOrganizableGroup = identifier;
NSMutableArray *newSavedGroup = [[NSMutableArray alloc] init];
// we don't need this, so that CASTLE and STORE can be in the same group
// [newSavedGroup addObject:specifiers[i - 1]];
[newSavedGroup addObject:s];
[organizableSpecifiers setObject:newSavedGroup forKey:currentOrganizableGroup];
}
else if (currentOrganizableGroup) {
[organizableSpecifiers[currentOrganizableGroup] addObject:s];
}
// else: We're above all that organizational confusion, and should stay out of it.
}
// If we're in the group specifier for the social accounts area, just pop that specifier into a new
// mutable array and get ready to shift things into it.
else if ([identifier isEqualToString:@"SOCIAL_ACCOUNTS"]) {
currentOrganizableGroup = identifier;
NSMutableArray *newSavedGroup = [[NSMutableArray alloc] init];
[newSavedGroup addObject:s];
[organizableSpecifiers setObject:newSavedGroup forKey:currentOrganizableGroup];
}
// If we've already encountered groups before, but THIS specifier is a group specifier, then it COULDN'T
// have been any previously encountered group, but is still important to PreferenceOrganizer's organization.
// So, it must either be the Tweaks or Apps section.
else if (currentOrganizableGroup) {
if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_8_0) {
// NSLog(@"currentOrganizableGroup = %@", currentOrganizableGroup);
// NSLog(@"identifier = %@", identifier);
if ([identifier isEqualToString:@"VIDEO_SUBSCRIBER_GROUP"]) {
deviceShowsTVProviders = 1;
}
// If the DDI is mounted, groupIDs will all shift down by 1, causing the categories to be sorted incorrectly.
// If an iOS 11 device is in a locale where the TV Provider option will show, groupID must be adjusted
if (groupID < 2 + ddiIsMounted + deviceShowsTVProviders) {
groupID++;
currentOrganizableGroup = @"STORE";
} else if (groupID == 2 + ddiIsMounted + deviceShowsTVProviders) {
groupID++;
currentOrganizableGroup = @"TWEAKS";
} else {
groupID++;
currentOrganizableGroup = @"APPS";
}
} else {
NSMutableArray *tweaksGroup = organizableSpecifiers[@"TWEAKS"];
if (tweaksGroup && tweaksGroup.count > 1) { // Because of some unholy lingering group specifiers
currentOrganizableGroup = @"APPS";
} else {
currentOrganizableGroup = @"TWEAKS";
}
}
NSMutableArray *newSavedGroup = organizableSpecifiers[currentOrganizableGroup];
if (!newSavedGroup) {
newSavedGroup = [[NSMutableArray alloc] init];
}
[newSavedGroup addObject:s];
// NSLog(@"Adding %@ to %@", s.identifier, currentOrganizableGroup);
[organizableSpecifiers setObject:newSavedGroup forKey:currentOrganizableGroup];
}
if (i == specifiers.count - 1 && groupID != 4 + ddiIsMounted) {
groupID++;
currentOrganizableGroup = @"APPS";
NSMutableArray *newSavedGroup = organizableSpecifiers[currentOrganizableGroup];
if (!newSavedGroup) {
newSavedGroup = [[NSMutableArray alloc] init];
}
[organizableSpecifiers setObject:newSavedGroup forKey:currentOrganizableGroup];
}
}
AppleAppSpecifiers = [organizableSpecifiers[(kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_11_0) ? @"STORE" : @"CASTLE"] retain];
if (kCFCoreFoundationVersionNumber < kCFCoreFoundationVersionNumber_iOS_11_0) {
[AppleAppSpecifiers addObjectsFromArray:organizableSpecifiers[@"STORE"]];
}
SocialAppSpecifiers = [organizableSpecifiers[@"SOCIAL_ACCOUNTS"] retain];
NSMutableArray *tweaksGroup = organizableSpecifiers[@"TWEAKS"];
if ([tweaksGroup count] != 0 && ((PSSpecifier *)tweaksGroup[0])->cellType == 0 && ((PSSpecifier *)tweaksGroup[1])->cellType == 0) {
[tweaksGroup removeObjectAtIndex:0];
}
TweakSpecifiers = [tweaksGroup retain];
AppStoreAppSpecifiers = [organizableSpecifiers[@"APPS"] retain];
// Shuffling START!!
// Make a group section for our special organized groups
[specifiers addObject:[PSSpecifier groupSpecifierWithName:nil]];
if (shouldShowAppleApps && AppleAppSpecifiers) {
if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_10_0) {
// Workaround for a bug in iOS 10
// If all Apple groups (APPLE_ACCOUNT_GROUP, etc.) are deleted, it will crash
for (PSSpecifier* specifier in AppleAppSpecifiers) {
// We'll handle this later in insertMovedThirdPartySpecifiersAnimated
if ([specifier.identifier isEqualToString:@"MEDIA_GROUP"] || [specifier.identifier isEqualToString:@"ACCOUNTS_GROUP"] || [specifier.identifier isEqualToString:@"APPLE_ACCOUNT_GROUP"]) {
continue;
} else {
[specifiers removeObject:specifier];
}
}
} else {
// Original behaviour is fine in iOS 9
[specifiers removeObjectsInArray:AppleAppSpecifiers];
}
PSSpecifier *appleSpecifier = [PSSpecifier preferenceSpecifierNamed:appleAppsLabel target:self set:NULL get:NULL detail:[AppleAppSpecifiersController class] cell:[PSTableCell cellTypeFromString:@"PSLinkCell"] edit:nil];
[appleSpecifier setProperty:[UIImage _applicationIconImageForBundleIdentifier:@"com.apple.mobilesafari" format:0 scale:[UIScreen mainScreen].scale] forKey:@"iconImage"];
// Setting this identifier for later use...
[appleSpecifier setIdentifier:@"APPLE_APPS"];
[specifiers addObject:appleSpecifier];
}
if (shouldShowSocialApps && SocialAppSpecifiers) {
[specifiers removeObjectsInArray:SocialAppSpecifiers];
PSSpecifier *socialSpecifier = [PSSpecifier preferenceSpecifierNamed:socialAppsLabel target:self set:NULL get:NULL detail:[SocialAppSpecifiersController class] cell:[PSTableCell cellTypeFromString:@"PSLinkCell"] edit:nil];
[socialSpecifier setProperty:[UIImage imageWithContentsOfFile:(kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_9_0) ? @"/System/Library/PrivateFrameworks/Preferences.framework/FacebookSettings.png" : @"/Applications/Preferences.app/FacebookSettings.png"] forKey:@"iconImage"];
[specifiers addObject:socialSpecifier];
}
if (shouldShowTweaks && TweakSpecifiers) {
[specifiers removeObjectsInArray:TweakSpecifiers];
PSSpecifier *cydiaSpecifier = [PSSpecifier preferenceSpecifierNamed:tweaksLabel target:self set:NULL get:NULL detail:[TweakSpecifiersController class] cell:[PSTableCell cellTypeFromString:@"PSLinkCell"] edit:nil];
[cydiaSpecifier setProperty:[UIImage imageWithContentsOfFile:@"/Library/PreferenceBundles/POPreferences.bundle/Tweaks.png"] forKey:@"iconImage"];
[specifiers addObject:cydiaSpecifier];
}
if (shouldShowAppStoreApps && AppStoreAppSpecifiers) {
[specifiers removeObjectsInArray:AppStoreAppSpecifiers];
PSSpecifier *appstoreSpecifier = [PSSpecifier preferenceSpecifierNamed:appStoreAppsLabel target:self set:NULL get:NULL detail:[AppStoreAppSpecifiersController class] cell:[PSTableCell cellTypeFromString:@"PSLinkCell"] edit:nil];
[appstoreSpecifier setProperty:[UIImage _applicationIconImageForBundleIdentifier:@"com.apple.AppStore" format:0 scale:[UIScreen mainScreen].scale] forKey:@"iconImage"];
[specifiers addObject:appstoreSpecifier];
}
if ((shouldShowAppleApps && AppleAppSpecifiers) && (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_10_0)) {
// Move deleted group specifiers to the end...
NSMutableArray *specifiersToRemove = [[NSMutableArray alloc] init];
for (int i = 0; i < specifiers.count; i++) {
PSSpecifier *specifier = (PSSpecifier *) specifiers[i];
NSString *identifier = specifier.identifier ?: @"";
// NSLog(@"specifier.identifier = %@",specifier.identifier);
if ([specifier.identifier isEqualToString:@"MEDIA_GROUP"] || [specifier.identifier isEqualToString:@"ACCOUNTS_GROUP"] || [specifier.identifier isEqualToString:@"APPLE_ACCOUNT_GROUP"]) {
// Move to the end only if DDI is mounted on iOS 10 (otherwise, the Settings app will crash for… some reason)
// That being said, this fix DOES cause a minor layout issue where "Wallet and Apple Pay" ends up sticking to the bottom of the PreferenceOrganiser 2 group, so I'll need to find a better solution for this later
if (((kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_10_0) && (kCFCoreFoundationVersionNumber < kCFCoreFoundationVersionNumber_iOS_11_0)) && ddiIsMounted) {
[specifiers removeObject:specifier];
[specifiers addObject:specifier];
} else {
[specifiersToRemove addObject:specifier];
}
}
}
[specifiers removeObjectsInArray:specifiersToRemove];
}
PO2Log([NSString stringWithFormat:@"organizableSpecifiers = %@", organizableSpecifiers], shouldSyslogSpam);
});
// If we found Apple's third party apps, we really won't add them because this would mess up the UITableView row count check after the update
if (shouldShowAppleApps) {
[specifiers removeObjectsInArray:[MSHookIvar<NSMutableDictionary *>(self, "_movedThirdPartySpecifiers") allValues]];
}
PO2Log([NSString stringWithFormat:@"shuffledSpecifiers = %@", specifiers], shouldSyslogSpam);
return specifiers;
}
// This method may add some Apple's third party specifiers with respect to restriction settings and results in duplicate entries, so fix it here
-(void) updateRestrictedSettings {
%orig();
if (shouldShowAppStoreApps) {
[((PSListController *)self).specifiers removeObjectsInArray:[MSHookIvar<NSMutableDictionary *>(self, "_movedThirdPartySpecifiers") allValues]];
removeOldAppleThirdPartySpecifiers(AppleAppSpecifiers);
[AppleAppSpecifiers addObjectsFromArray:[MSHookIvar<NSMutableDictionary *>(self, "_movedThirdPartySpecifiers") allValues]];
}
}
// Write custom -loadView method implementation that works with unorganised specifiers... which somehow fixes the infamous iOS 9.x iPad crash bug
// However, PreferenceLoader ultimately should be updated in order to fix the insertion bug present on iOS 9.x iPads, as stated by vit9696 (#9)
-(void) loadView {
NSMutableArray *originalSpecifiers = MSHookIvar<NSMutableArray *>(self, "_specifiers");
MSHookIvar<NSMutableArray *>(self, "_specifiers") = unorganisedSpecifiers;
%orig();
MSHookIvar<NSMutableArray *>(self, "_specifiers") = originalSpecifiers;
}
%end
%end
%hook PrefsListController
%group iOS9Up
// Redirect all of Apple's third party specifiers to AppleAppSpecifiers
-(void) insertMovedThirdPartySpecifiersAnimated:(BOOL)animated {
if ((kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_10_0) && (kCFCoreFoundationVersionNumber < kCFCoreFoundationVersionNumber_iOS_11_0) && (shouldShowAppleApps && AppleAppSpecifiers)) {
// Appears to be the cause behind the resume-from-suspend crash on iOS 11 (as long as the _thirdPartySpecifiers condition in -(NSMutableArray *) specifiers is present)
removeOldAppleGroupSpecifiers([self specifiers]);
}
if (shouldShowAppleApps && AppleAppSpecifiers.count) {
NSArray <PSSpecifier *> *movedThirdPartySpecifiers = [MSHookIvar<NSMutableDictionary *>(self, "_movedThirdPartySpecifiers") allValues];
removeOldAppleThirdPartySpecifiers(AppleAppSpecifiers);
[AppleAppSpecifiers addObjectsFromArray:movedThirdPartySpecifiers];
} else {
%orig(animated);
}
}
-(void) _reallyLoadThirdPartySpecifiersForProxies:(NSArray *)apps withCompletion:(void (^)(NSArray <PSSpecifier *> *thirdParty, NSDictionary *appleThirdParty))completion {
// thirdParty - self->_thirdPartySpecifiers
// appleThirdParty - self->_movedThirdPartySpecifiers
void (^newCompletion)(NSArray <PSSpecifier *> *, NSDictionary *) = ^(NSArray <PSSpecifier *> *thirdParty, NSDictionary *appleThirdParty) {
if (completion) {
completion(thirdParty, appleThirdParty);
}
fixupThirdPartySpecifiers(self, thirdParty, appleThirdParty);
};
%orig(apps, newCompletion);
}
// iOS 10 renamed this method, should be benign hooking this on iOS 9 as it wouldn't exist
// Somewhat bad practice in that this is a literal copy-pasta of the above code, but this'll have to do for now until I figure out a better method of doing so
-(void) _reallyLoadThirdPartySpecifiersForApps:(NSArray *)apps withCompletion:(void (^)(NSArray <PSSpecifier *> *thirdParty, NSDictionary *appleThirdParty))completion {
// thirdParty - self->_thirdPartySpecifiers
// appleThirdParty - self->_movedThirdPartySpecifiers
void (^newCompletion)(NSArray <PSSpecifier *> *, NSDictionary *) = ^(NSArray <PSSpecifier *> *thirdParty, NSDictionary *appleThirdParty) {
if (completion) {
completion(thirdParty, appleThirdParty);
}
fixupThirdPartySpecifiers(self, thirdParty, appleThirdParty);
};
%orig(apps, newCompletion);
}
// Turns out that Apple renamed the method /again/ in iOS 14…
// https://developer.limneos.net/?ios=14.4&framework=PreferencesUI.framework&header=PSUIPrefsListController.h
-(void) _reallyLoadThirdPartySpecifiersForApps:(NSArray *)apps shouldAddAppClipSpecifier:(BOOL)specifier withCompletion:(void (^)(NSArray <PSSpecifier *> *thirdParty, NSDictionary *appleThirdParty))completion {
// thirdParty - self->_thirdPartySpecifiers
// appleThirdParty - self->_movedThirdPartySpecifiers
void (^newCompletion)(NSArray <PSSpecifier *> *, NSDictionary *) = ^(NSArray <PSSpecifier *> *thirdParty, NSDictionary *appleThirdParty) {
if (completion) {
completion(thirdParty, appleThirdParty);
}
fixupThirdPartySpecifiers(self, thirdParty, appleThirdParty);
};
%orig(apps, specifier, newCompletion);
}
%end
%group iOS78
-(void) insertMovedThirdPartySpecifiersAtStartIndex:(NSUInteger)index usingInsertBlock:(id)arg2 andExistenceBlock:(id)arg3 {
if (shouldShowAppStoreApps && AppStoreAppSpecifiers.count) {
[AppStoreAppSpecifiers removeObjectsInArray:[MSHookIvar<NSMutableDictionary *>(self, "_movedThirdPartySpecifiers") allValues]];
[AppStoreAppSpecifiers addObjectsFromArray:[MSHookIvar<NSMutableDictionary *>(self, "_movedThirdPartySpecifiers") allValues]];
} else {
%orig(index, arg2, arg3);
}
}
-(void) _reallyLoadThirdPartySpecifiersForProxies:(NSArray *)apps withCompletion:(void (^)())completion {
void (^newCompletion)() = ^(void) {
if (completion)
completion();
NSArray <PSSpecifier *> *thirdParty = MSHookIvar<NSArray <PSSpecifier *> *>(self, "_thirdPartySpecifiers");
NSDictionary *appleThirdParty = MSHookIvar<NSDictionary *>(self, "_movedThirdPartySpecifiers");
fixupThirdPartySpecifiers(self, thirdParty, appleThirdParty);
};
%orig(apps, newCompletion);
}
-(void) reloadSpecifiers {
return;
}
%end
%end
/*
## ####### ###### #######
## ## ## ## ##
## ## ## ## ##
## ## ## ###### #######
## ## ## ## ## ##
## ## ## ## ## ## ##
## ####### ###### ######
*/
%group iOS6
%hook PrefsListController
-(void) _setupiCloudSpecifier:(PSSpecifier *)specifier {
if (specifier == nil) {
return;
}
%orig();
}
-(NSMutableArray *) specifiers {
NSMutableArray *specifiers = %orig();
PO2Log([NSString stringWithFormat:@"originalSpecifiers = %@", specifiers], shouldSyslogSpam);
PO2InitPrefs();
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSMutableDictionary *savedSpecifiers = [NSMutableDictionary dictionary];
NSInteger group = -1;
for (PSSpecifier *s in specifiers) {
if (s->cellType == 0) {
group++;
if (group >= 3) {
[savedSpecifiers setObject:[NSMutableArray array] forKey:[NSNumber numberWithInteger:group]];
} else {
continue;
}
}
if (group >= 3) {
[[savedSpecifiers objectForKey:[NSNumber numberWithInteger:group]] addObject:s];
}
}
AppleAppSpecifiers = [[savedSpecifiers objectForKey:[NSNumber numberWithInteger:3]] retain];
[AppleAppSpecifiers addObjectsFromArray:[savedSpecifiers objectForKey:[NSNumber numberWithInteger:4]]];
SocialAppSpecifiers = [[savedSpecifiers objectForKey:[NSNumber numberWithInteger:5]] retain];
AppStoreAppSpecifiers = [[savedSpecifiers objectForKey:[NSNumber numberWithInteger:group]] retain];
if (group - 2 >= 6) {
TweakSpecifiers = [[savedSpecifiers objectForKey:[NSNumber numberWithInteger:group - 2]] retain];
} else if (group - 1 >= 6) {
TweakSpecifiers = [[savedSpecifiers objectForKey:[NSNumber numberWithInteger:group - 1]] retain];
}
[specifiers addObject:[PSSpecifier groupSpecifierWithName:nil]];
if (shouldShowAppleApps) {
if (AppleAppSpecifiers.count > 0) {
[specifiers removeObjectsInArray:AppleAppSpecifiers];
[AppleAppSpecifiers removeObjectAtIndex:0];
PSSpecifier *appleSpecifier = [PSSpecifier preferenceSpecifierNamed:appleAppsLabel target:self set:NULL get:NULL detail:[AppleAppSpecifiersController class] cell:[PSTableCell cellTypeFromString:@"PSLinkCell"] edit:nil];
[appleSpecifier setProperty:[UIImage _applicationIconImageForBundleIdentifier:@"com.apple.mobilesafari" format:0 scale:[UIScreen mainScreen].scale] forKey:@"iconImage"];
[specifiers addObject:appleSpecifier];
}
}
if (shouldShowSocialApps) {
if (SocialAppSpecifiers.count > 0) {
[specifiers removeObjectsInArray:SocialAppSpecifiers];
[SocialAppSpecifiers removeObjectAtIndex:0];
PSSpecifier *socialSpecifier = [PSSpecifier preferenceSpecifierNamed:socialAppsLabel target:self set:NULL get:NULL detail:[SocialAppSpecifiersController class] cell:[PSTableCell cellTypeFromString:@"PSLinkCell"] edit:nil];
[socialSpecifier setProperty:[UIImage imageWithContentsOfFile:@"/Applications/Preferences.app/FacebookSettings.png"] forKey:@"iconImage"];
[specifiers addObject:socialSpecifier];
}
}
if (shouldShowTweaks) {
if (TweakSpecifiers.count > 0) {
[specifiers removeObjectsInArray:TweakSpecifiers];
[TweakSpecifiers removeObjectAtIndex:0];
PSSpecifier *cydiaSpecifier = [PSSpecifier preferenceSpecifierNamed:tweaksLabel target:self set:NULL get:NULL detail:[TweakSpecifiersController class] cell:[PSTableCell cellTypeFromString:@"PSLinkCell"] edit:nil];
[cydiaSpecifier setProperty:[UIImage imageWithContentsOfFile:@"/Library/PreferenceBundles/POPreferences.bundle/Tweaks.png"] forKey:@"iconImage"];
[specifiers addObject:cydiaSpecifier];
}
}
if (shouldShowAppStoreApps) {
if (AppStoreAppSpecifiers.count > 0) {
[specifiers removeObjectsInArray:AppStoreAppSpecifiers];
[AppStoreAppSpecifiers removeObjectAtIndex:0];
PSSpecifier *appstoreSpecifier = [PSSpecifier preferenceSpecifierNamed:appStoreAppsLabel target:self set:NULL get:NULL detail:[AppStoreAppSpecifiersController class] cell:[PSTableCell cellTypeFromString:@"PSLinkCell"] edit:nil];
[appstoreSpecifier setProperty:[UIImage _applicationIconImageForBundleIdentifier:@"com.apple.AppStore" format:0 scale:[UIScreen mainScreen].scale] forKey:@"iconImage"];
[specifiers addObject:appstoreSpecifier];
}
}
PO2Log([NSString stringWithFormat:@"savedSpecifiers = %@", savedSpecifiers], shouldSyslogSpam);
});
PO2Log([NSString stringWithFormat:@"shuffledSpecifiers = %@", specifiers], shouldSyslogSpam);
return specifiers;
}
-(void) refresh3rdPartyBundles {
%orig();
NSMutableArray *savedSpecifiers = [NSMutableArray array];
BOOL go = 0;
for (PSSpecifier *s in MSHookIvar<NSMutableArray *>(self, "_specifiers")) {
if (!go && [s.identifier isEqualToString:@"App Store"]) {
go = 1;
continue;
}
if (go) {
[savedSpecifiers addObject:s];
}
}
for (PSSpecifier *s in savedSpecifiers) {
[self removeSpecifier:s];
}
[savedSpecifiers removeObjectAtIndex:0];
[AppStoreAppSpecifiers release];
AppStoreAppSpecifiers = [savedSpecifiers retain];
}
-(void) reloadSpecifiers {
return;
}
%end
%end
%hook PreferencesAppController
%new
// Push the requested tweak specifier controller.
-(BOOL) preferenceOrganizerOpenTweakPane:(NSString *)name {
// Replace the percent escapes in an iOS 6-friendly way (deprecated in iOS 9).
name = [name stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
// Set up return value.
BOOL foundMatch = NO;
// Loop the registered TweakSpecifiers.
for (PSSpecifier *specifier in TweakSpecifiers) {
// If we have a match, and that match has a non-nil target, let's do this.
if ([name caseInsensitiveCompare:[specifier name]] == NSOrderedSame && [specifier target]) {
// We have a valid match.
foundMatch = YES;
// Push the requested controller.
[[[specifier target] navigationController] pushViewController:[[specifier target] controllerForSpecifier:specifier] animated:NO];
// Get the specifier for TweaksSpecifier.
PSSpecifier *tweaksSpecifier = [[[self rootController] rootListController] specifierForID:tweaksLabel];
// If we got a specifier for TweaksSpecifier...
if (tweaksSpecifier) {
// Get the TweakSpecifiersController.
TweakSpecifiersController *tweakSpecifiersController = [[[self rootController] rootListController] controllerForSpecifier:tweaksSpecifier];
// If we got a controller for TweakSpecifiers...
if (tweakSpecifiersController) {
// Get the navigation stack count.
int stackCount = [[specifier target] navigationController].viewControllers.count;
// Declare a NSMutableArray to manipulate the navigation stack (if necessary).
NSMutableArray *mutableStack;
// Switch on the navigation stack count and manipulate the stack accordingly.
switch (stackCount) {
// Three controllers in the navigation stack (rootListController, unknown controller, and controllerForSpecifier).
// Check the controller at index 1 and replace it if necessary.
case 3:
// If the user was already on the TweakSpecifiersController, then we're good.
if (![[[[specifier target] navigationController].viewControllers objectAtIndex:1] isMemberOfClass:[TweakSpecifiersController class]]) {
// Get a mutable copy of the navigation stack.
mutableStack = [NSMutableArray arrayWithArray:[[specifier target] navigationController].viewControllers];
// Set the TweakSpecifiersController navigationItem title.
[[tweakSpecifiersController navigationItem] setTitle: tweaksLabel];
// Replace the intermediate controller with the TweakSpecifiersController.
[mutableStack replaceObjectAtIndex:1 withObject:tweakSpecifiersController];
// Update the navigation stack.
[[specifier target] navigationController].viewControllers = [NSArray arrayWithArray:mutableStack];
//NSLog(@"PO2: preferenceOrganizerOpenTweakPane: replace the intermediate controller with the TweakSpecifiersController.");
}
break;
// Two controllers in the navigation stack (rootListController and controllerForSpecifier).
// Insert the TweakSpecifiersController as an intermediate.
case 2:
// Get a mutable copy of the navigation stack.
mutableStack = [NSMutableArray arrayWithArray:[[specifier target] navigationController].viewControllers];
// Set the TweakSpecifiersController navigationItem title.
[[tweakSpecifiersController navigationItem] setTitle: tweaksLabel];
// Insert the TweakSpecifiersController as an intermediate controller.
[mutableStack insertObject:tweakSpecifiersController atIndex: 1];
// Update the navigation stack.
[[specifier target] navigationController].viewControllers = [NSArray arrayWithArray:mutableStack];
break;
// One controller in the navigation stack should not be possible after we push the controllerForSpecifier,
// and zero controllers is legitimately impossible.
case 1:
case 0:
// Get out of here!
break;
// Too many controllers to manage. Dump everything in the navigation stack except the first and last controllers.
default:
// Get a mutable copy of the navigation stack.
mutableStack = [NSMutableArray arrayWithArray:[[specifier target] navigationController].viewControllers];
// Remove everything in the middle.
[mutableStack removeObjectsInRange:NSMakeRange(1, stackCount - 2)];
// Set the TweakSpecifiersController navigationItem title.
[[tweakSpecifiersController navigationItem] setTitle: tweaksLabel];
// Insert the TweakSpecifiersController as an intermediate controller.
[mutableStack insertObject:tweakSpecifiersController atIndex: 1];
// Update the navigation stack.
[[specifier target] navigationController].viewControllers = [NSArray arrayWithArray:mutableStack];
}
}
}
// Break the loop.
break;
}
}
// Return success or failure.
return foundMatch;
}
// Parses the given URL to check if it's in a PreferenceOrganizer2-API conforming format, that is to say,
// it has a root=Tweaks, and a &path= corresponding to a tweak name.
// If %path= is present and it points to a valid tweak name, try to launch it.
// If preferenceOrganizerOpenTweakPane fails, just open the root tweak pane (even if they've renamed it).
-(void) applicationOpenURL:(NSURL *)url {
NSString *parsableURL = [url absoluteString];
if (parsableURL.length >= 11 && [parsableURL rangeOfString:@"root=Tweaks"].location != NSNotFound) {
NSString *truncatedPrefsURL = [@"prefs:root=" stringByAppendingString:[tweaksLabel stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
url = [NSURL URLWithString:truncatedPrefsURL];
NSRange tweakPathRange = [parsableURL rangeOfString:@"path="];
if (tweakPathRange.location != NSNotFound) {
NSInteger tweakPathOrigin = tweakPathRange.location + tweakPathRange.length;
// If specified tweak was found, don't call the original method;
if ([self preferenceOrganizerOpenTweakPane:[parsableURL substringWithRange:NSMakeRange(tweakPathOrigin, parsableURL.length - tweakPathOrigin)]]) {
return;
}
}
}
%orig(url);
}
%end
%ctor {
PO2Log([NSString stringWithFormat:@"kCFCoreFoundationVersionNumber = %f", kCFCoreFoundationVersionNumber], shouldSyslogSpam);
if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_7_0) {
%init(iOS7Up, PrefsListController = objc_getClass((kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_9_0) ? "PSUIPrefsListController" : "PrefsListController"));
if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_9_0) {
%init(iOS9Up, PrefsListController = objc_getClass("PSUIPrefsListController"));
} else {
%init(iOS78);
}
} else {
%init(iOS6);
}
%init();
PO2InitPrefs();
PO2Observer(PO2InitPrefs, "net.angelxwind.preferenceorganizer2-PreferencesChanged");
}