1 /* WinterBoard - Theme Manager for the iPhone
2 * Copyright (C) 2009 Jay Freeman (saurik)
6 * Redistribution and use in source and binary
7 * forms, with or without modification, are permitted
8 * provided that the following conditions are met:
10 * 1. Redistributions of source code must retain the
11 * above copyright notice, this list of conditions
12 * and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the
14 * above copyright notice, this list of conditions
15 * and the following disclaimer in the documentation
16 * and/or other materials provided with the
18 * 3. The name of the author may not be used to endorse
19 * or promote products derived from this software
20 * without specific prior written permission.
22 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS''
23 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
24 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
25 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE
27 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
28 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
29 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
30 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
31 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
32 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
33 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
34 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
35 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38 #import <Foundation/Foundation.h>
39 #import <UIKit/UIKit.h>
40 #import <Preferences/PSListController.h>
41 #import <Preferences/PSSpecifier.h>
42 #import <Preferences/PSTableCell.h>
43 #import <UIKit/UINavigationButton.h>
47 static BOOL (*IsIconHiddenDisplayId)(NSString *);
48 static BOOL (*HideIconViaDisplayId)(NSString *);
49 static BOOL (*UnHideIconViaDisplayId)(NSString *);
51 static const NSString *WinterBoardDisplayID = @"com.saurik.WinterBoard";
53 extern NSString *PSTableCellKey;
54 extern "C" UIImage *_UIImageWithName(NSString *);
56 static UIImage *checkImage;
57 static UIImage *uncheckedImage;
59 static BOOL settingsChanged;
60 static NSMutableDictionary *_settings;
61 static NSString *_plist;
63 /* [NSObject yieldToSelector:(withObject:)] {{{*/
64 @interface NSObject (wb$yieldToSelector)
65 - (id) wb$yieldToSelector:(SEL)selector withObject:(id)object;
66 - (id) wb$yieldToSelector:(SEL)selector;
69 @implementation NSObject (Cydia)
71 - (void) wb$doNothing {
74 - (void) wb$_yieldToContext:(NSMutableArray *)context {
75 NSAutoreleasePool *pool([[NSAutoreleasePool alloc] init]);
77 SEL selector(reinterpret_cast<SEL>([[context objectAtIndex:0] pointerValue]));
78 id object([[context objectAtIndex:1] nonretainedObjectValue]);
79 volatile bool &stopped(*reinterpret_cast<bool *>([[context objectAtIndex:2] pointerValue]));
81 /* XXX: deal with exceptions */
82 id value([self performSelector:selector withObject:object]);
84 NSMethodSignature *signature([self methodSignatureForSelector:selector]);
85 [context removeAllObjects];
86 if ([signature methodReturnLength] != 0 && value != nil)
87 [context addObject:value];
92 performSelectorOnMainThread:@selector(wb$doNothing)
100 - (id) wb$yieldToSelector:(SEL)selector withObject:(id)object {
101 /*return [self performSelector:selector withObject:object];*/
103 volatile bool stopped(false);
105 NSMutableArray *context([NSMutableArray arrayWithObjects:
106 [NSValue valueWithPointer:selector],
107 [NSValue valueWithNonretainedObject:object],
108 [NSValue valueWithPointer:const_cast<bool *>(&stopped)],
111 NSThread *thread([[[NSThread alloc]
113 selector:@selector(wb$_yieldToContext:)
119 NSRunLoop *loop([NSRunLoop currentRunLoop]);
120 NSDate *future([NSDate distantFuture]);
122 while (!stopped && [loop runMode:NSDefaultRunLoopMode beforeDate:future]);
124 return [context count] == 0 ? nil : [context objectAtIndex:0];
127 - (id) wb$yieldToSelector:(SEL)selector {
128 return [self wb$yieldToSelector:selector withObject:nil];
134 /* Theme Settings Controller {{{ */
135 @interface WBSThemesController: PSViewController <UITableViewDelegate, UITableViewDataSource> {
136 UITableView *_tableView;
137 NSMutableArray *_themes;
140 @property (nonatomic, retain) NSMutableArray *themes;
144 - (id) initForContentSize:(CGSize)size;
146 - (id) navigationTitle;
147 - (void) themesChanged;
149 - (int) numberOfSectionsInTableView:(UITableView *)tableView;
150 - (id) tableView:(UITableView *)tableView titleForHeaderInSection:(int)section;
151 - (int) tableView:(UITableView *)tableView numberOfRowsInSection:(int)section;
152 - (id) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
153 - (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath;
154 - (void) tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath;
155 - (UITableViewCellEditingStyle) tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath;
156 - (BOOL) tableView:(UITableView *)tableView shouldIndentWhileEditingRowAtIndexPath:(NSIndexPath *)indexPath;
157 - (BOOL) tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath;
161 @implementation WBSThemesController
163 @synthesize themes = _themes;
166 NSAutoreleasePool *pool([[NSAutoreleasePool alloc] init]);
167 checkImage = [_UIImageWithName(@"UIPreferencesBlueCheck.png") retain];
168 uncheckedImage = [[UIImage imageWithContentsOfFile:@"/System/Library/PreferenceBundles/WinterBoardSettings.bundle/SearchResultsCheckmarkClear.png"] retain];
172 - (id) initForContentSize:(CGSize)size {
173 if ((self = [super initForContentSize:size]) != nil) {
174 self.themes = [_settings objectForKey:@"Themes"];
176 if (NSString *theme = [_settings objectForKey:@"Theme"]) {
177 self.themes = [NSMutableArray arrayWithObject:
178 [NSMutableDictionary dictionaryWithObjectsAndKeys:
180 [NSNumber numberWithBool:YES], @"Active", nil]];
181 [_settings removeObjectForKey:@"Theme"];
184 self.themes = [NSMutableArray array];
185 [_settings setObject:_themes forKey:@"Themes"];
188 NSMutableArray *themesOnDisk([NSMutableArray array]);
191 addObjectsFromArray:[[NSFileManager defaultManager]
192 contentsOfDirectoryAtPath:@"/Library/Themes" error:NULL]
195 [themesOnDisk addObjectsFromArray:[[NSFileManager defaultManager]
196 contentsOfDirectoryAtPath:[NSString stringWithFormat:@"%@/Library/SummerBoard/Themes", NSHomeDirectory()]
200 for (int i = 0, count = [themesOnDisk count]; i < count; i++) {
201 NSString *theme = [themesOnDisk objectAtIndex:i];
202 if ([theme hasSuffix:@".theme"])
203 [themesOnDisk replaceObjectAtIndex:i withObject:[theme stringByDeletingPathExtension]];
206 NSMutableSet *themesSet([NSMutableSet set]);
208 for (int i = 0, count = [_themes count]; i < count; i++) {
209 NSDictionary *theme([_themes objectAtIndex:i]);
210 NSString *name([theme objectForKey:@"Name"]);
212 if (!name || ![themesOnDisk containsObject:name]) {
213 [_themes removeObjectAtIndex:i];
217 [themesSet addObject:name];
221 for (NSString *theme in themesOnDisk) {
222 if ([themesSet containsObject:theme])
224 [themesSet addObject:theme];
226 [_themes insertObject:[NSMutableDictionary dictionaryWithObjectsAndKeys:
228 [NSNumber numberWithBool:NO], @"Active",
232 _tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, 320, 480-64) style:UITableViewStyleGrouped];
233 [_tableView setDataSource:self];
234 [_tableView setDelegate:self];
235 [_tableView setEditing:YES];
236 [_tableView setAllowsSelectionDuringEditing:YES];
237 [self showLeftButton:@"WinterBoard" withStyle:1 rightButton:nil withStyle:0];
243 [_tableView release];
248 - (id) navigationTitle {
256 - (void) themesChanged {
257 settingsChanged = YES;
260 /* UITableViewDelegate / UITableViewDataSource Methods {{{ */
261 - (int) numberOfSectionsInTableView:(UITableView *)tableView {
265 - (id) tableView:(UITableView *)tableView titleForHeaderInSection:(int)section {
269 - (int) tableView:(UITableView *)tableView numberOfRowsInSection:(int)section {
270 return _themes.count;
273 - (id) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
274 UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"ThemeCell"];
276 cell = [[[UITableViewCell alloc] initWithFrame:CGRectMake(0, 0, 100, 100) reuseIdentifier:@"ThemeCell"] autorelease];
277 //[cell setTableViewStyle:UITableViewCellStyleDefault];
280 NSDictionary *theme([_themes objectAtIndex:indexPath.row]);
281 cell.text = [theme objectForKey:@"Name"];
282 cell.hidesAccessoryWhenEditing = NO;
283 NSNumber *active([theme objectForKey:@"Active"]);
284 BOOL inactive(active == nil || ![active boolValue]);
285 [cell setImage:(inactive ? uncheckedImage : checkImage)];
289 - (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
290 UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
291 NSMutableDictionary *theme = [_themes objectAtIndex:indexPath.row];
292 NSNumber *active = [theme objectForKey:@"Active"];
293 BOOL inactive = active == nil || ![active boolValue];
294 [theme setObject:[NSNumber numberWithBool:inactive] forKey:@"Active"];
295 [cell setImage:(!inactive ? uncheckedImage : checkImage)];
296 [tableView deselectRowAtIndexPath:(NSIndexPath *)indexPath animated:YES];
297 [self themesChanged];
300 - (void) tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath {
301 NSUInteger fromIndex = [fromIndexPath row];
302 NSUInteger toIndex = [toIndexPath row];
303 if (fromIndex == toIndex)
305 NSMutableDictionary *theme = [[[_themes objectAtIndex:fromIndex] retain] autorelease];
306 [_themes removeObjectAtIndex:fromIndex];
307 [_themes insertObject:theme atIndex:toIndex];
308 [self themesChanged];
311 - (UITableViewCellEditingStyle) tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath {
312 return UITableViewCellEditingStyleNone;
315 - (BOOL) tableView:(UITableView *)tableView shouldIndentWhileEditingRowAtIndexPath:(NSIndexPath *)indexPath {
319 - (BOOL) tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath {
326 @interface WBSettingsController: PSListController {
329 - (id) initForContentSize:(CGSize)size;
332 - (void) navigationBarButtonClicked:(int)buttonIndex;
333 - (void) viewWillRedisplay;
334 - (void) pushController:(id)controller;
336 - (void) settingsChanged;
337 - (NSString *) title;
338 - (void) setPreferenceValue:(id)value specifier:(PSSpecifier *)spec;
339 - (id) readPreferenceValue:(PSSpecifier *)spec;
343 @implementation WBSettingsController
346 void *libhide(dlopen("/usr/lib/hide.dylib", RTLD_LAZY));
347 IsIconHiddenDisplayId = reinterpret_cast<BOOL (*)(NSString *)>(dlsym(libhide, "IsIconHiddenDisplayId"));
348 HideIconViaDisplayId = reinterpret_cast<BOOL (*)(NSString *)>(dlsym(libhide, "HideIconViaDisplayId"));
349 UnHideIconViaDisplayId = reinterpret_cast<BOOL (*)(NSString *)>(dlsym(libhide, "UnHideIconViaDisplayId"));
352 - (id) initForContentSize:(CGSize)size {
353 if ((self = [super initForContentSize:size]) != nil) {
354 _plist = [[NSString stringWithFormat:@"%@/Library/Preferences/com.saurik.WinterBoard.plist", NSHomeDirectory()] retain];
355 _settings = [([NSMutableDictionary dictionaryWithContentsOfFile:_plist] ?: [NSMutableDictionary dictionary]) retain];
357 [_settings setObject:[NSNumber numberWithBool:IsIconHiddenDisplayId(WinterBoardDisplayID)] forKey:@"IconHidden"];
367 - (void) _optimizeThemes {
368 system("/usr/libexec/winterboard/Optimize");
371 - (void) optimizeThemes {
372 UIView *view([self view]);
373 UIWindow *window([view window]);
375 UIProgressHUD *hud([[[UIProgressHUD alloc] initWithWindow:window] autorelease]);
376 [hud setText:@"Reticulating Splines\nPlease Wait (Minutes)"];
378 [window setUserInteractionEnabled:NO];
380 [window addSubview:hud];
382 [self wb$yieldToSelector:@selector(_optimizeThemes)];
383 [hud removeFromSuperview];
385 [window setUserInteractionEnabled:YES];
387 [self settingsChanged];
391 if (!settingsChanged)
394 NSData *data([NSPropertyListSerialization dataFromPropertyList:_settings format:NSPropertyListBinaryFormat_v1_0 errorDescription:NULL]);
397 if (![data writeToFile:_plist options:NSAtomicWrite error:NULL])
400 ([[_settings objectForKey:@"IconHidden"] boolValue] ? HideIconViaDisplayId : UnHideIconViaDisplayId)(WinterBoardDisplayID);
402 unlink("/User/Library/Caches/com.apple.springboard-imagecache-icons");
403 unlink("/User/Library/Caches/com.apple.springboard-imagecache-icons.plist");
404 unlink("/User/Library/Caches/com.apple.springboard-imagecache-smallicons");
405 unlink("/User/Library/Caches/com.apple.springboard-imagecache-smallicons.plist");
407 system("rm -rf /User/Library/Caches/SpringBoardIconCache");
408 system("rm -rf /User/Library/Caches/SpringBoardIconCache-small");
410 system("killall SpringBoard");
413 - (void) navigationBarButtonClicked:(int)buttonIndex {
414 if (!settingsChanged) {
415 [super navigationBarButtonClicked:buttonIndex];
419 if (buttonIndex == 0)
420 settingsChanged = NO;
423 [self.rootController popController];
426 - (void) viewWillRedisplay {
428 [self settingsChanged];
429 [super viewWillRedisplay];
432 - (void) pushController:(id)controller {
433 [self hideNavigationBarButtons];
434 [super pushController:controller];
439 _specifiers = [[self loadSpecifiersFromPlistName:@"WinterBoard" target:self] retain];
443 - (void) settingsChanged {
444 [self showLeftButton:@"Respring" withStyle:2 rightButton:@"Cancel" withStyle:0];
445 settingsChanged = YES;
448 - (NSString *) title {
449 return @"WinterBoard";
452 - (void) setPreferenceValue:(id)value specifier:(PSSpecifier *)spec {
453 NSString *key([spec propertyForKey:@"key"]);
454 if ([[spec propertyForKey:@"negate"] boolValue])
455 value = [NSNumber numberWithBool:(![value boolValue])];
456 [_settings setValue:value forKey:key];
457 [self settingsChanged];
460 - (id) readPreferenceValue:(PSSpecifier *)spec {
461 NSString *key([spec propertyForKey:@"key"]);
462 id defaultValue([spec propertyForKey:@"default"]);
463 id plistValue([_settings objectForKey:key]);
466 if ([[spec propertyForKey:@"negate"] boolValue])
467 plistValue = [NSNumber numberWithBool:(![plistValue boolValue])];