]> git.saurik.com Git - winterboard.git/blame - Settings.mm
If only Apple "groked" compilers, I could use ARC.
[winterboard.git] / Settings.mm
CommitLineData
224d48e3 1/* WinterBoard - Theme Manager for the iPhone
900dc9a5 2 * Copyright (C) 2008-2014 Jay Freeman (saurik)
224d48e3
JF
3*/
4
900dc9a5 5/* GNU Lesser General Public License, Version 3 {{{ */
224d48e3 6/*
900dc9a5
JF
7 * WinterBoard is free software: you can redistribute it and/or modify it under
8 * the terms of the GNU Lesser General Public License as published by the
9 * Free Software Foundation, either version 3 of the License, or (at your
10 * option) any later version.
224d48e3 11 *
900dc9a5
JF
12 * WinterBoard is distributed in the hope that it will be useful, but WITHOUT
13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
15 * License for more details.
224d48e3 16 *
900dc9a5
JF
17 * You should have received a copy of the GNU Lesser General Public License
18 * along with WinterBoard. If not, see <http://www.gnu.org/licenses/>.
19**/
20/* }}} */
224d48e3
JF
21
22#import <Foundation/Foundation.h>
23#import <UIKit/UIKit.h>
b39b993d
DH
24#import <Preferences/PSRootController.h>
25#import <Preferences/PSViewController.h>
224d48e3
JF
26#import <Preferences/PSListController.h>
27#import <Preferences/PSSpecifier.h>
28#import <Preferences/PSTableCell.h>
29#import <UIKit/UINavigationButton.h>
30
c4bc3f11 31#include <cmath>
7c88a3f1 32#include <dlfcn.h>
b39b993d 33#include <objc/runtime.h>
7c88a3f1 34
224d48e3
JF
35extern NSString *PSTableCellKey;
36extern "C" UIImage *_UIImageWithName(NSString *);
37
38static UIImage *checkImage;
39static UIImage *uncheckedImage;
40
41static BOOL settingsChanged;
42static NSMutableDictionary *_settings;
43static NSString *_plist;
44
deed0017
JF
45void AddThemes(NSMutableArray *themesOnDisk, NSString *folder) {
46 NSArray *themes([[NSFileManager defaultManager] contentsOfDirectoryAtPath:folder error:NULL]);
47 for (NSString *theme in themes) {
48 if (NSDictionary *info = [NSDictionary dictionaryWithContentsOfFile:[NSString stringWithFormat:@"%@/%@/Info.plist", folder, theme]]) {
49 if (NSArray *version = [info objectForKey:@"CoreFoundationVersion"]) {
50 size_t count([version count]);
51 if (count == 0 || count > 2)
52 continue;
53
54 double lower([[version objectAtIndex:0] doubleValue]);
55 if (kCFCoreFoundationVersionNumber < lower)
56 continue;
57
58 if (count != 1) {
59 double upper([[version objectAtIndex:1] doubleValue]);
60 if (upper <= kCFCoreFoundationVersionNumber)
61 continue;
62 }
63 }
64 }
65
66 [themesOnDisk addObject:theme];
67 }
68}
69
265e19b2
JF
70/* [NSObject yieldToSelector:(withObject:)] {{{*/
71@interface NSObject (wb$yieldToSelector)
72- (id) wb$yieldToSelector:(SEL)selector withObject:(id)object;
73- (id) wb$yieldToSelector:(SEL)selector;
74@end
75
76@implementation NSObject (Cydia)
77
78- (void) wb$doNothing {
79}
80
81- (void) wb$_yieldToContext:(NSMutableArray *)context {
82 NSAutoreleasePool *pool([[NSAutoreleasePool alloc] init]);
83
84 SEL selector(reinterpret_cast<SEL>([[context objectAtIndex:0] pointerValue]));
85 id object([[context objectAtIndex:1] nonretainedObjectValue]);
86 volatile bool &stopped(*reinterpret_cast<bool *>([[context objectAtIndex:2] pointerValue]));
87
88 /* XXX: deal with exceptions */
89 id value([self performSelector:selector withObject:object]);
90
91 NSMethodSignature *signature([self methodSignatureForSelector:selector]);
92 [context removeAllObjects];
93 if ([signature methodReturnLength] != 0 && value != nil)
94 [context addObject:value];
95
96 stopped = true;
97
98 [self
99 performSelectorOnMainThread:@selector(wb$doNothing)
100 withObject:nil
101 waitUntilDone:NO
102 ];
103
104 [pool release];
105}
106
107- (id) wb$yieldToSelector:(SEL)selector withObject:(id)object {
108 /*return [self performSelector:selector withObject:object];*/
109
110 volatile bool stopped(false);
111
112 NSMutableArray *context([NSMutableArray arrayWithObjects:
113 [NSValue valueWithPointer:selector],
114 [NSValue valueWithNonretainedObject:object],
115 [NSValue valueWithPointer:const_cast<bool *>(&stopped)],
116 nil]);
117
118 NSThread *thread([[[NSThread alloc]
119 initWithTarget:self
120 selector:@selector(wb$_yieldToContext:)
121 object:context
122 ] autorelease]);
123
124 [thread start];
125
126 NSRunLoop *loop([NSRunLoop currentRunLoop]);
127 NSDate *future([NSDate distantFuture]);
128
129 while (!stopped && [loop runMode:NSDefaultRunLoopMode beforeDate:future]);
130
131 return [context count] == 0 ? nil : [context objectAtIndex:0];
132}
133
134- (id) wb$yieldToSelector:(SEL)selector {
135 return [self wb$yieldToSelector:selector withObject:nil];
136}
137
138@end
139/* }}} */
140
224d48e3 141/* Theme Settings Controller {{{ */
f4b021a2 142@interface WBSThemesTableViewCell : UITableViewCell {
c4bc3f11
JF
143 UIImageView *checkmark_;
144 UIImageView *icon_;
145 UILabel *name_;
f4b021a2
JF
146}
147
148@end
149
150@implementation WBSThemesTableViewCell
151
c4bc3f11 152- (void) dealloc {
c4bc3f11
JF
153 [checkmark_ release];
154 [icon_ release];
155 [name_ release];
10ee6126 156 [super dealloc];
c4bc3f11
JF
157}
158
f4b021a2
JF
159- (id) initWithFrame:(CGRect)frame reuseIdentifier:(NSString *)reuse {
160 if ((self = [super initWithFrame:frame reuseIdentifier:reuse]) != nil) {
c4bc3f11
JF
161 CGFloat border(48), check(40), icon(64);
162 UIView *content([self contentView]);
163 CGSize size([content frame].size);
164
165 checkmark_ = [[UIImageView alloc] initWithFrame:CGRectMake(std::floor((border - check) / 2), 0, check, size.height)];
166 [checkmark_ setAutoresizingMask:UIViewAutoresizingFlexibleHeight];
167 [content addSubview:checkmark_];
168
169 name_ = [[UILabel alloc] initWithFrame:CGRectMake(border, 0, 0, size.height)];
170 [name_ setAutoresizingMask:(UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight)];
171 [content addSubview:name_];
172
173 icon_ = [[UIImageView alloc] initWithFrame:CGRectMake(size.width - icon - 48, 0, icon, icon)];
174 [content addSubview:icon_];
f4b021a2
JF
175 } return self;
176}
177
c4bc3f11
JF
178- (void) setCheck:(bool)inactive {
179 [self setImage:(inactive ? uncheckedImage : checkImage)];
180}
181
f4b021a2 182- (void) setTheme:(NSDictionary *)theme {
c4bc3f11
JF
183 [name_ setText:[theme objectForKey:@"Name"]];
184
f4b021a2
JF
185 NSNumber *active([theme objectForKey:@"Active"]);
186 BOOL inactive(active == nil || ![active boolValue]);
c4bc3f11
JF
187 [self setCheck:inactive];
188
189 CGRect area([name_ frame]);
190 area.size.width = ([icon_ image] == nil ? self.contentView.frame.size.width : icon_.frame.origin.x) - area.origin.x - 9;
191 [name_ setFrame:area];
f4b021a2
JF
192}
193
194@end
195
224d48e3
JF
196@interface WBSThemesController: PSViewController <UITableViewDelegate, UITableViewDataSource> {
197 UITableView *_tableView;
198 NSMutableArray *_themes;
199}
200
201@property (nonatomic, retain) NSMutableArray *themes;
202
224d48e3
JF
203- (id) initForContentSize:(CGSize)size;
204- (id) view;
205- (id) navigationTitle;
206- (void) themesChanged;
207
208- (int) numberOfSectionsInTableView:(UITableView *)tableView;
209- (id) tableView:(UITableView *)tableView titleForHeaderInSection:(int)section;
210- (int) tableView:(UITableView *)tableView numberOfRowsInSection:(int)section;
211- (id) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
212- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath;
213- (void) tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath;
214- (UITableViewCellEditingStyle) tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath;
215- (BOOL) tableView:(UITableView *)tableView shouldIndentWhileEditingRowAtIndexPath:(NSIndexPath *)indexPath;
216- (BOOL) tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath;
7c88a3f1 217
224d48e3
JF
218@end
219
220@implementation WBSThemesController
221
222@synthesize themes = _themes;
223
06839c7d 224+ (void) initialize {
224d48e3
JF
225 NSAutoreleasePool *pool([[NSAutoreleasePool alloc] init]);
226 checkImage = [_UIImageWithName(@"UIPreferencesBlueCheck.png") retain];
227 uncheckedImage = [[UIImage imageWithContentsOfFile:@"/System/Library/PreferenceBundles/WinterBoardSettings.bundle/SearchResultsCheckmarkClear.png"] retain];
228 [pool release];
229}
230
231- (id) initForContentSize:(CGSize)size {
232 if ((self = [super initForContentSize:size]) != nil) {
233 self.themes = [_settings objectForKey:@"Themes"];
234 if (!_themes) {
235 if (NSString *theme = [_settings objectForKey:@"Theme"]) {
236 self.themes = [NSMutableArray arrayWithObject:
237 [NSMutableDictionary dictionaryWithObjectsAndKeys:
238 theme, @"Name",
239 [NSNumber numberWithBool:YES], @"Active", nil]];
240 [_settings removeObjectForKey:@"Theme"];
241 }
242 if (!_themes)
243 self.themes = [NSMutableArray array];
244 [_settings setObject:_themes forKey:@"Themes"];
245 }
246
247 NSMutableArray *themesOnDisk([NSMutableArray array]);
deed0017
JF
248 AddThemes(themesOnDisk, @"/Library/Themes");
249 AddThemes(themesOnDisk, [NSString stringWithFormat:@"%@/Library/SummerBoard/Themes", NSHomeDirectory()]);
224d48e3
JF
250
251 for (int i = 0, count = [themesOnDisk count]; i < count; i++) {
252 NSString *theme = [themesOnDisk objectAtIndex:i];
253 if ([theme hasSuffix:@".theme"])
254 [themesOnDisk replaceObjectAtIndex:i withObject:[theme stringByDeletingPathExtension]];
255 }
256
257 NSMutableSet *themesSet([NSMutableSet set]);
258
259 for (int i = 0, count = [_themes count]; i < count; i++) {
260 NSDictionary *theme([_themes objectAtIndex:i]);
261 NSString *name([theme objectForKey:@"Name"]);
262
263 if (!name || ![themesOnDisk containsObject:name]) {
264 [_themes removeObjectAtIndex:i];
265 i--;
266 count--;
267 } else {
268 [themesSet addObject:name];
269 }
270 }
271
272 for (NSString *theme in themesOnDisk) {
273 if ([themesSet containsObject:theme])
274 continue;
275 [themesSet addObject:theme];
276
277 [_themes insertObject:[NSMutableDictionary dictionaryWithObjectsAndKeys:
278 theme, @"Name",
279 [NSNumber numberWithBool:NO], @"Active",
280 nil] atIndex:0];
281 }
282
c4bc3f11
JF
283 _tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, 320, 480-64) style:UITableViewStylePlain];
284 [_tableView setRowHeight:48];
224d48e3
JF
285 [_tableView setDataSource:self];
286 [_tableView setDelegate:self];
287 [_tableView setEditing:YES];
288 [_tableView setAllowsSelectionDuringEditing:YES];
b39b993d
DH
289 if ([self respondsToSelector:@selector(setView:)])
290 [self setView:_tableView];
224d48e3
JF
291 }
292 return self;
293}
294
295- (void) dealloc {
296 [_tableView release];
297 [_themes release];
298 [super dealloc];
299}
300
301- (id) navigationTitle {
302 return @"Themes";
303}
304
305- (id) view {
306 return _tableView;
307}
308
309- (void) themesChanged {
310 settingsChanged = YES;
311}
312
313/* UITableViewDelegate / UITableViewDataSource Methods {{{ */
314- (int) numberOfSectionsInTableView:(UITableView *)tableView {
315 return 1;
316}
317
318- (id) tableView:(UITableView *)tableView titleForHeaderInSection:(int)section {
319 return nil;
320}
321
322- (int) tableView:(UITableView *)tableView numberOfRowsInSection:(int)section {
323 return _themes.count;
324}
325
326- (id) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
f4b021a2 327 WBSThemesTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"ThemeCell"];
224d48e3 328 if (!cell) {
f4b021a2 329 cell = [[[WBSThemesTableViewCell alloc] initWithFrame:CGRectMake(0, 0, 100, 100) reuseIdentifier:@"ThemeCell"] autorelease];
224d48e3
JF
330 //[cell setTableViewStyle:UITableViewCellStyleDefault];
331 }
332
333 NSDictionary *theme([_themes objectAtIndex:indexPath.row]);
f4b021a2 334 [cell setTheme:theme];
224d48e3
JF
335 return cell;
336}
337
338- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
339 UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
340 NSMutableDictionary *theme = [_themes objectAtIndex:indexPath.row];
341 NSNumber *active = [theme objectForKey:@"Active"];
342 BOOL inactive = active == nil || ![active boolValue];
343 [theme setObject:[NSNumber numberWithBool:inactive] forKey:@"Active"];
344 [cell setImage:(!inactive ? uncheckedImage : checkImage)];
345 [tableView deselectRowAtIndexPath:(NSIndexPath *)indexPath animated:YES];
346 [self themesChanged];
347}
348
349- (void) tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath {
350 NSUInteger fromIndex = [fromIndexPath row];
351 NSUInteger toIndex = [toIndexPath row];
352 if (fromIndex == toIndex)
353 return;
354 NSMutableDictionary *theme = [[[_themes objectAtIndex:fromIndex] retain] autorelease];
355 [_themes removeObjectAtIndex:fromIndex];
356 [_themes insertObject:theme atIndex:toIndex];
357 [self themesChanged];
358}
359
360- (UITableViewCellEditingStyle) tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath {
361 return UITableViewCellEditingStyleNone;
362}
363
364- (BOOL) tableView:(UITableView *)tableView shouldIndentWhileEditingRowAtIndexPath:(NSIndexPath *)indexPath {
365 return NO;
366}
367
368- (BOOL) tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath {
369 return YES;
370}
371/* }}} */
372@end
373/* }}} */
374
024f98ea 375@interface WBAdvancedController: PSListController {
224d48e3
JF
376}
377
224d48e3
JF
378- (id) specifiers;
379- (void) settingsChanged;
224d48e3
JF
380
381@end
382
024f98ea 383@implementation WBAdvancedController
224d48e3 384
024f98ea
JF
385- (id) specifiers {
386 if (!_specifiers)
387 _specifiers = [[self loadSpecifiersFromPlistName:@"Advanced" target:self] retain];
388 return _specifiers;
7c88a3f1
JF
389}
390
024f98ea
JF
391- (void) settingsChanged {
392 settingsChanged = YES;
393}
7c88a3f1 394
024f98ea
JF
395- (void) setPreferenceValue:(id)value specifier:(PSSpecifier *)spec {
396 NSString *key([spec propertyForKey:@"key"]);
397 if ([[spec propertyForKey:@"negate"] boolValue])
398 value = [NSNumber numberWithBool:(![value boolValue])];
399 [_settings setValue:value forKey:key];
400 [self settingsChanged];
224d48e3
JF
401}
402
024f98ea
JF
403- (id) readPreferenceValue:(PSSpecifier *)spec {
404 NSString *key([spec propertyForKey:@"key"]);
405 id defaultValue([spec propertyForKey:@"default"]);
406 id plistValue([_settings objectForKey:key]);
407 if (!plistValue)
408 return defaultValue;
409 if ([[spec propertyForKey:@"negate"] boolValue])
410 plistValue = [NSNumber numberWithBool:(![plistValue boolValue])];
411 return plistValue;
224d48e3
JF
412}
413
43fd3a0b 414- (void) __optimizeThemes {
265e19b2
JF
415 system("/usr/libexec/winterboard/Optimize");
416}
417
418- (void) optimizeThemes {
54a4cf27 419 UIAlertView *alert([[[UIAlertView alloc]
43fd3a0b 420 initWithTitle:@"Optimize Themes"
54a4cf27 421 message:@"Please note that this setting /replaces/ the PNG files that came with the theme. PNG files that have been iPhone-optimized cannot be viewed on a normal computer unless they are first deoptimized. You can use Cydia to reinstall themes that have been optimized in order to revert to the original PNG files."
43fd3a0b 422 delegate:self
54a4cf27
JF
423 cancelButtonTitle:@"Cancel"
424 otherButtonTitles:@"Optimize", nil
43fd3a0b
JF
425 ] autorelease]);
426
54a4cf27
JF
427 [alert setContext:@"optimize"];
428 [alert setNumberOfRows:1];
429 [alert show];
43fd3a0b
JF
430}
431
432- (void) _optimizeThemes {
265e19b2
JF
433 UIView *view([self view]);
434 UIWindow *window([view window]);
435
436 UIProgressHUD *hud([[[UIProgressHUD alloc] initWithWindow:window] autorelease]);
437 [hud setText:@"Reticulating Splines\nPlease Wait (Minutes)"];
438
439 [window setUserInteractionEnabled:NO];
440
441 [window addSubview:hud];
442 [hud show:YES];
43fd3a0b 443 [self wb$yieldToSelector:@selector(__optimizeThemes)];
265e19b2
JF
444 [hud removeFromSuperview];
445
446 [window setUserInteractionEnabled:YES];
447
448 [self settingsChanged];
449}
450
54a4cf27
JF
451- (void) alertView:(UIAlertView *)alert clickedButtonAtIndex:(NSInteger)button {
452 NSString *context([alert context]);
43fd3a0b
JF
453
454 if ([context isEqualToString:@"optimize"]) {
54a4cf27
JF
455 if (button == [alert firstOtherButtonIndex]) {
456 [self performSelector:@selector(_optimizeThemes) withObject:nil afterDelay:0];
43fd3a0b
JF
457 }
458
54a4cf27
JF
459 [alert dismissWithClickedButtonIndex:-1 animated:YES];
460 }
461 /*else if ([super respondsToSelector:@selector(alertView:clickedButtonAtIndex:)])
462 [super alertView:alert clickedButtonAtIndex:button];*/
43fd3a0b
JF
463}
464
024f98ea
JF
465@end
466
467@interface WBSettingsController: PSListController {
468}
469
470- (id) initForContentSize:(CGSize)size;
471- (void) dealloc;
472- (void) suspend;
473- (void) navigationBarButtonClicked:(int)buttonIndex;
474- (void) viewWillRedisplay;
475- (void) pushController:(id)controller;
476- (id) specifiers;
477- (void) settingsChanged;
478- (NSString *) title;
479- (void) setPreferenceValue:(id)value specifier:(PSSpecifier *)spec;
480- (id) readPreferenceValue:(PSSpecifier *)spec;
481
482@end
483
484@implementation WBSettingsController
485
34b51da8
JF
486- (void) _wb$loadSettings {
487 _plist = [[NSString stringWithFormat:@"%@/Library/Preferences/com.saurik.WinterBoard.plist", NSHomeDirectory()] retain];
488 _settings = [NSMutableDictionary dictionaryWithContentsOfFile:_plist];
489
490 bool set;
491 if (_settings != nil)
492 set = true;
493 else {
494 set = false;
495 _settings = [NSMutableDictionary dictionary];
496 }
d236b808 497
34b51da8 498 _settings = [_settings retain];
d236b808 499
34b51da8
JF
500 if ([_settings objectForKey:@"SummerBoard"] == nil)
501 [_settings setObject:[NSNumber numberWithBool:set] forKey:@"SummerBoard"];
34b51da8
JF
502}
503
504- (id) initForContentSize:(CGSize)size {
505 if ((self = [super initForContentSize:size]) != nil) {
506 [self _wb$loadSettings];
024f98ea
JF
507 } return self;
508}
509
510- (void) dealloc {
511 [_settings release];
512 [_plist release];
513 [super dealloc];
514}
515
224d48e3
JF
516- (void) suspend {
517 if (!settingsChanged)
518 return;
519
520 NSData *data([NSPropertyListSerialization dataFromPropertyList:_settings format:NSPropertyListBinaryFormat_v1_0 errorDescription:NULL]);
521 if (!data)
522 return;
523 if (![data writeToFile:_plist options:NSAtomicWrite error:NULL])
524 return;
525
526 unlink("/User/Library/Caches/com.apple.springboard-imagecache-icons");
527 unlink("/User/Library/Caches/com.apple.springboard-imagecache-icons.plist");
528 unlink("/User/Library/Caches/com.apple.springboard-imagecache-smallicons");
529 unlink("/User/Library/Caches/com.apple.springboard-imagecache-smallicons.plist");
bbea033b 530
546a205b
JF
531 unlink("/User/Library/Caches/com.apple.SpringBoard.folderSwitcherLinen");
532 unlink("/User/Library/Caches/com.apple.SpringBoard.notificationCenterLinen");
bbea033b 533
546a205b
JF
534 unlink("/User/Library/Caches/com.apple.SpringBoard.folderSwitcherLinen.0");
535 unlink("/User/Library/Caches/com.apple.SpringBoard.folderSwitcherLinen.1");
536 unlink("/User/Library/Caches/com.apple.SpringBoard.folderSwitcherLinen.2");
537 unlink("/User/Library/Caches/com.apple.SpringBoard.folderSwitcherLinen.3");
7c88a3f1
JF
538
539 system("rm -rf /User/Library/Caches/SpringBoardIconCache");
540 system("rm -rf /User/Library/Caches/SpringBoardIconCache-small");
f64efe8d 541 system("rm -rf /User/Library/Caches/com.apple.IconsCache");
546a205b 542 system("rm -rf /User/Library/Caches/com.apple.newsstand");
bbea033b 543 system("rm -rf /User/Library/Caches/com.apple.springboard.sharedimagecache");
9c3f0992 544 system("rm -rf /User/Library/Caches/com.apple.UIStatusBar");
7c88a3f1 545
3da4212c
JF
546 system("rm -rf /User/Library/Caches/BarDialer");
547 system("rm -rf /User/Library/Caches/BarDialer_selected");
548 system("rm -rf /User/Library/Caches/BarRecents");
549 system("rm -rf /User/Library/Caches/BarRecents_selected");
550 system("rm -rf /User/Library/Caches/BarVM");
551 system("rm -rf /User/Library/Caches/BarVM_selected");
552
26bd2bf2 553 system("killall -9 lsd");
c532a1cf
JF
554
555 if (kCFCoreFoundationVersionNumber > 700) // XXX: iOS 6.x
556 system("killall backboardd");
557 else
558 system("killall SpringBoard");
224d48e3
JF
559}
560
b39b993d
DH
561- (void) cancelChanges {
562 [_settings release];
563 [_plist release];
b39b993d 564
34b51da8
JF
565 [self _wb$loadSettings];
566
b39b993d
DH
567 [self reloadSpecifiers];
568 if (![[PSViewController class] instancesRespondToSelector:@selector(showLeftButton:withStyle:rightButton:withStyle:)]) {
1d3b613f
JF
569 [[self navigationItem] setLeftBarButtonItem:nil];
570 [[self navigationItem] setRightBarButtonItem:nil];
838dbf4c
DH
571 } else {
572 [self showLeftButton:nil withStyle:0 rightButton:nil withStyle:0];
b39b993d
DH
573 }
574 settingsChanged = NO;
575}
576
224d48e3
JF
577- (void) navigationBarButtonClicked:(int)buttonIndex {
578 if (!settingsChanged) {
579 [super navigationBarButtonClicked:buttonIndex];
580 return;
581 }
582
838dbf4c 583 if (buttonIndex == 0) {
b39b993d 584 [self cancelChanges];
838dbf4c
DH
585 return;
586 }
224d48e3
JF
587
588 [self suspend];
589 [self.rootController popController];
590}
591
b39b993d
DH
592- (void) settingsConfirmButtonClicked:(UIBarButtonItem *)button {
593 [self navigationBarButtonClicked:button.tag];
594}
595
224d48e3
JF
596- (void) viewWillRedisplay {
597 if (settingsChanged)
598 [self settingsChanged];
599 [super viewWillRedisplay];
600}
601
b39b993d
DH
602- (void) viewWillAppear:(BOOL)animated {
603 if (settingsChanged)
604 [self settingsChanged];
1d3b613f
JF
605 if ([super respondsToSelector:@selector(viewWillAppear:)])
606 [super viewWillAppear:animated];
b39b993d
DH
607}
608
224d48e3
JF
609- (void) pushController:(id)controller {
610 [self hideNavigationBarButtons];
611 [super pushController:controller];
612}
613
614- (id) specifiers {
6e9f4e02
JF
615 if (!_specifiers) {
616 NSMutableArray *specifiers([NSMutableArray array]);
617 for (PSSpecifier *specifier in [self loadSpecifiersFromPlistName:@"WinterBoard" target:self]) {
618 if (NSArray *version = [specifier propertyForKey:@"wb$filter"]) {
619 size_t count([version count]);
620 if (count == 0 || count > 2)
621 continue;
622
623 double lower([[version objectAtIndex:0] doubleValue]);
624 if (kCFCoreFoundationVersionNumber < lower)
625 continue;
626
627 if (count != 1) {
628 double upper([[version objectAtIndex:1] doubleValue]);
629 if (upper <= kCFCoreFoundationVersionNumber)
630 continue;
631 }
632 }
633 [specifiers addObject:specifier];
634 }
635 _specifiers = [specifiers retain];
636 }
224d48e3
JF
637 return _specifiers;
638}
639
640- (void) settingsChanged {
b39b993d
DH
641 if (![[PSViewController class] instancesRespondToSelector:@selector(showLeftButton:withStyle:rightButton:withStyle:)]) {
642 UIBarButtonItem *respringButton([[UIBarButtonItem alloc] initWithTitle:@"Respring" style:UIBarButtonItemStyleDone target:self action:@selector(settingsConfirmButtonClicked:)]);
643 UIBarButtonItem *cancelButton([[UIBarButtonItem alloc] initWithTitle:@"Cancel" style:UIBarButtonItemStylePlain target:self action:@selector(settingsConfirmButtonClicked:)]);
644 cancelButton.tag = 0;
645 respringButton.tag = 1;
1d3b613f
JF
646 [[self navigationItem] setLeftBarButtonItem:respringButton];
647 [[self navigationItem] setRightBarButtonItem:cancelButton];
b39b993d
DH
648 [respringButton release];
649 [cancelButton release];
650 } else {
651 [self showLeftButton:@"Respring" withStyle:2 rightButton:@"Cancel" withStyle:0];
652 }
224d48e3
JF
653 settingsChanged = YES;
654}
655
656- (NSString *) title {
657 return @"WinterBoard";
658}
659
660- (void) setPreferenceValue:(id)value specifier:(PSSpecifier *)spec {
7c88a3f1 661 NSString *key([spec propertyForKey:@"key"]);
224d48e3
JF
662 if ([[spec propertyForKey:@"negate"] boolValue])
663 value = [NSNumber numberWithBool:(![value boolValue])];
7c88a3f1 664 [_settings setValue:value forKey:key];
224d48e3
JF
665 [self settingsChanged];
666}
667
668- (id) readPreferenceValue:(PSSpecifier *)spec {
669 NSString *key([spec propertyForKey:@"key"]);
670 id defaultValue([spec propertyForKey:@"default"]);
671 id plistValue([_settings objectForKey:key]);
672 if (!plistValue)
673 return defaultValue;
674 if ([[spec propertyForKey:@"negate"] boolValue])
675 plistValue = [NSNumber numberWithBool:(![plistValue boolValue])];
676 return plistValue;
677}
678
679@end
b39b993d
DH
680
681#define WBSAddMethod(_class, _sel, _imp, _type) \
682 if (![[_class class] instancesRespondToSelector:@selector(_sel)]) \
683 class_addMethod([_class class], @selector(_sel), (IMP)_imp, _type)
684void $PSRootController$popController(PSRootController *self, SEL _cmd) {
685 [self popViewControllerAnimated:YES];
686}
687
688void $PSViewController$hideNavigationBarButtons(PSRootController *self, SEL _cmd) {
689}
690
691id $PSViewController$initForContentSize$(PSRootController *self, SEL _cmd, CGRect contentSize) {
692 return [self init];
693}
694
695static __attribute__((constructor)) void __wbsInit() {
696 WBSAddMethod(PSRootController, popController, $PSRootController$popController, "v@:");
697 WBSAddMethod(PSViewController, hideNavigationBarButtons, $PSViewController$hideNavigationBarButtons, "v@:");
698 WBSAddMethod(PSViewController, initForContentSize:, $PSViewController$initForContentSize$, "@@:{ff}");
699}