]> git.saurik.com Git - winterboard.git/blame - Settings.mm
Fix video wallpaper.
[winterboard.git] / Settings.mm
CommitLineData
224d48e3
JF
1/* WinterBoard - Theme Manager for the iPhone
2 * Copyright (C) 2009 Jay Freeman (saurik)
3*/
4
5/*
6 * Redistribution and use in source and binary
7 * forms, with or without modification, are permitted
8 * provided that the following conditions are met:
9 *
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
17 * distribution.
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.
21 *
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.
36*/
37
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>
44
7c88a3f1
JF
45#include <dlfcn.h>
46
47static BOOL (*IsIconHiddenDisplayId)(NSString *);
48static BOOL (*HideIconViaDisplayId)(NSString *);
49static BOOL (*UnHideIconViaDisplayId)(NSString *);
50
51static const NSString *WinterBoardDisplayID = @"com.saurik.WinterBoard";
52
224d48e3
JF
53extern NSString *PSTableCellKey;
54extern "C" UIImage *_UIImageWithName(NSString *);
55
56static UIImage *checkImage;
57static UIImage *uncheckedImage;
58
59static BOOL settingsChanged;
60static NSMutableDictionary *_settings;
61static NSString *_plist;
62
265e19b2
JF
63/* [NSObject yieldToSelector:(withObject:)] {{{*/
64@interface NSObject (wb$yieldToSelector)
65- (id) wb$yieldToSelector:(SEL)selector withObject:(id)object;
66- (id) wb$yieldToSelector:(SEL)selector;
67@end
68
69@implementation NSObject (Cydia)
70
71- (void) wb$doNothing {
72}
73
74- (void) wb$_yieldToContext:(NSMutableArray *)context {
75 NSAutoreleasePool *pool([[NSAutoreleasePool alloc] init]);
76
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]));
80
81 /* XXX: deal with exceptions */
82 id value([self performSelector:selector withObject:object]);
83
84 NSMethodSignature *signature([self methodSignatureForSelector:selector]);
85 [context removeAllObjects];
86 if ([signature methodReturnLength] != 0 && value != nil)
87 [context addObject:value];
88
89 stopped = true;
90
91 [self
92 performSelectorOnMainThread:@selector(wb$doNothing)
93 withObject:nil
94 waitUntilDone:NO
95 ];
96
97 [pool release];
98}
99
100- (id) wb$yieldToSelector:(SEL)selector withObject:(id)object {
101 /*return [self performSelector:selector withObject:object];*/
102
103 volatile bool stopped(false);
104
105 NSMutableArray *context([NSMutableArray arrayWithObjects:
106 [NSValue valueWithPointer:selector],
107 [NSValue valueWithNonretainedObject:object],
108 [NSValue valueWithPointer:const_cast<bool *>(&stopped)],
109 nil]);
110
111 NSThread *thread([[[NSThread alloc]
112 initWithTarget:self
113 selector:@selector(wb$_yieldToContext:)
114 object:context
115 ] autorelease]);
116
117 [thread start];
118
119 NSRunLoop *loop([NSRunLoop currentRunLoop]);
120 NSDate *future([NSDate distantFuture]);
121
122 while (!stopped && [loop runMode:NSDefaultRunLoopMode beforeDate:future]);
123
124 return [context count] == 0 ? nil : [context objectAtIndex:0];
125}
126
127- (id) wb$yieldToSelector:(SEL)selector {
128 return [self wb$yieldToSelector:selector withObject:nil];
129}
130
131@end
132/* }}} */
133
224d48e3
JF
134/* Theme Settings Controller {{{ */
135@interface WBSThemesController: PSViewController <UITableViewDelegate, UITableViewDataSource> {
136 UITableView *_tableView;
137 NSMutableArray *_themes;
138}
139
140@property (nonatomic, retain) NSMutableArray *themes;
141
142+ (void) load;
143
144- (id) initForContentSize:(CGSize)size;
145- (id) view;
146- (id) navigationTitle;
147- (void) themesChanged;
148
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;
7c88a3f1 158
224d48e3
JF
159@end
160
161@implementation WBSThemesController
162
163@synthesize themes = _themes;
164
165+ (void) load {
166 NSAutoreleasePool *pool([[NSAutoreleasePool alloc] init]);
167 checkImage = [_UIImageWithName(@"UIPreferencesBlueCheck.png") retain];
168 uncheckedImage = [[UIImage imageWithContentsOfFile:@"/System/Library/PreferenceBundles/WinterBoardSettings.bundle/SearchResultsCheckmarkClear.png"] retain];
169 [pool release];
170}
171
172- (id) initForContentSize:(CGSize)size {
173 if ((self = [super initForContentSize:size]) != nil) {
174 self.themes = [_settings objectForKey:@"Themes"];
175 if (!_themes) {
176 if (NSString *theme = [_settings objectForKey:@"Theme"]) {
177 self.themes = [NSMutableArray arrayWithObject:
178 [NSMutableDictionary dictionaryWithObjectsAndKeys:
179 theme, @"Name",
180 [NSNumber numberWithBool:YES], @"Active", nil]];
181 [_settings removeObjectForKey:@"Theme"];
182 }
183 if (!_themes)
184 self.themes = [NSMutableArray array];
185 [_settings setObject:_themes forKey:@"Themes"];
186 }
187
188 NSMutableArray *themesOnDisk([NSMutableArray array]);
189
190 [themesOnDisk
191 addObjectsFromArray:[[NSFileManager defaultManager]
192 contentsOfDirectoryAtPath:@"/Library/Themes" error:NULL]
193 ];
194
195 [themesOnDisk addObjectsFromArray:[[NSFileManager defaultManager]
196 contentsOfDirectoryAtPath:[NSString stringWithFormat:@"%@/Library/SummerBoard/Themes", NSHomeDirectory()]
197 error:NULL
198 ]];
199
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]];
204 }
205
206 NSMutableSet *themesSet([NSMutableSet set]);
207
208 for (int i = 0, count = [_themes count]; i < count; i++) {
209 NSDictionary *theme([_themes objectAtIndex:i]);
210 NSString *name([theme objectForKey:@"Name"]);
211
212 if (!name || ![themesOnDisk containsObject:name]) {
213 [_themes removeObjectAtIndex:i];
214 i--;
215 count--;
216 } else {
217 [themesSet addObject:name];
218 }
219 }
220
221 for (NSString *theme in themesOnDisk) {
222 if ([themesSet containsObject:theme])
223 continue;
224 [themesSet addObject:theme];
225
226 [_themes insertObject:[NSMutableDictionary dictionaryWithObjectsAndKeys:
227 theme, @"Name",
228 [NSNumber numberWithBool:NO], @"Active",
229 nil] atIndex:0];
230 }
231
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];
238 }
239 return self;
240}
241
242- (void) dealloc {
243 [_tableView release];
244 [_themes release];
245 [super dealloc];
246}
247
248- (id) navigationTitle {
249 return @"Themes";
250}
251
252- (id) view {
253 return _tableView;
254}
255
256- (void) themesChanged {
257 settingsChanged = YES;
258}
259
260/* UITableViewDelegate / UITableViewDataSource Methods {{{ */
261- (int) numberOfSectionsInTableView:(UITableView *)tableView {
262 return 1;
263}
264
265- (id) tableView:(UITableView *)tableView titleForHeaderInSection:(int)section {
266 return nil;
267}
268
269- (int) tableView:(UITableView *)tableView numberOfRowsInSection:(int)section {
270 return _themes.count;
271}
272
273- (id) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
274 UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"ThemeCell"];
275 if (!cell) {
276 cell = [[[UITableViewCell alloc] initWithFrame:CGRectMake(0, 0, 100, 100) reuseIdentifier:@"ThemeCell"] autorelease];
277 //[cell setTableViewStyle:UITableViewCellStyleDefault];
278 }
279
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)];
286 return cell;
287}
288
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];
298}
299
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)
304 return;
305 NSMutableDictionary *theme = [[[_themes objectAtIndex:fromIndex] retain] autorelease];
306 [_themes removeObjectAtIndex:fromIndex];
307 [_themes insertObject:theme atIndex:toIndex];
308 [self themesChanged];
309}
310
311- (UITableViewCellEditingStyle) tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath {
312 return UITableViewCellEditingStyleNone;
313}
314
315- (BOOL) tableView:(UITableView *)tableView shouldIndentWhileEditingRowAtIndexPath:(NSIndexPath *)indexPath {
316 return NO;
317}
318
319- (BOOL) tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath {
320 return YES;
321}
322/* }}} */
323@end
324/* }}} */
325
326@interface WBSettingsController: PSListController {
327}
328
329- (id) initForContentSize:(CGSize)size;
330- (void) dealloc;
331- (void) suspend;
332- (void) navigationBarButtonClicked:(int)buttonIndex;
333- (void) viewWillRedisplay;
334- (void) pushController:(id)controller;
335- (id) specifiers;
336- (void) settingsChanged;
337- (NSString *) title;
338- (void) setPreferenceValue:(id)value specifier:(PSSpecifier *)spec;
339- (id) readPreferenceValue:(PSSpecifier *)spec;
340
341@end
342
343@implementation WBSettingsController
344
7c88a3f1 345+ (void) load {
7c88a3f1 346 void *libhide(dlopen("/usr/lib/hide.dylib", RTLD_LAZY));
7c88a3f1 347 IsIconHiddenDisplayId = reinterpret_cast<BOOL (*)(NSString *)>(dlsym(libhide, "IsIconHiddenDisplayId"));
7c88a3f1 348 HideIconViaDisplayId = reinterpret_cast<BOOL (*)(NSString *)>(dlsym(libhide, "HideIconViaDisplayId"));
7c88a3f1 349 UnHideIconViaDisplayId = reinterpret_cast<BOOL (*)(NSString *)>(dlsym(libhide, "UnHideIconViaDisplayId"));
7c88a3f1
JF
350}
351
224d48e3
JF
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];
7c88a3f1
JF
356
357 [_settings setObject:[NSNumber numberWithBool:IsIconHiddenDisplayId(WinterBoardDisplayID)] forKey:@"IconHidden"];
224d48e3
JF
358 } return self;
359}
360
361- (void) dealloc {
362 [_settings release];
363 [_plist release];
364 [super dealloc];
365}
366
265e19b2
JF
367- (void) _optimizeThemes {
368 system("/usr/libexec/winterboard/Optimize");
369}
370
371- (void) optimizeThemes {
372 UIView *view([self view]);
373 UIWindow *window([view window]);
374
375 UIProgressHUD *hud([[[UIProgressHUD alloc] initWithWindow:window] autorelease]);
376 [hud setText:@"Reticulating Splines\nPlease Wait (Minutes)"];
377
378 [window setUserInteractionEnabled:NO];
379
380 [window addSubview:hud];
381 [hud show:YES];
382 [self wb$yieldToSelector:@selector(_optimizeThemes)];
383 [hud removeFromSuperview];
384
385 [window setUserInteractionEnabled:YES];
386
387 [self settingsChanged];
388}
389
224d48e3
JF
390- (void) suspend {
391 if (!settingsChanged)
392 return;
393
394 NSData *data([NSPropertyListSerialization dataFromPropertyList:_settings format:NSPropertyListBinaryFormat_v1_0 errorDescription:NULL]);
395 if (!data)
396 return;
397 if (![data writeToFile:_plist options:NSAtomicWrite error:NULL])
398 return;
399
7c88a3f1
JF
400 ([[_settings objectForKey:@"IconHidden"] boolValue] ? HideIconViaDisplayId : UnHideIconViaDisplayId)(WinterBoardDisplayID);
401
224d48e3
JF
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");
7c88a3f1
JF
406
407 system("rm -rf /User/Library/Caches/SpringBoardIconCache");
408 system("rm -rf /User/Library/Caches/SpringBoardIconCache-small");
409
224d48e3
JF
410 system("killall SpringBoard");
411}
412
413- (void) navigationBarButtonClicked:(int)buttonIndex {
414 if (!settingsChanged) {
415 [super navigationBarButtonClicked:buttonIndex];
416 return;
417 }
418
419 if (buttonIndex == 0)
420 settingsChanged = NO;
421
422 [self suspend];
423 [self.rootController popController];
424}
425
426- (void) viewWillRedisplay {
427 if (settingsChanged)
428 [self settingsChanged];
429 [super viewWillRedisplay];
430}
431
432- (void) pushController:(id)controller {
433 [self hideNavigationBarButtons];
434 [super pushController:controller];
435}
436
437- (id) specifiers {
438 if (!_specifiers)
439 _specifiers = [[self loadSpecifiersFromPlistName:@"WinterBoard" target:self] retain];
440 return _specifiers;
441}
442
443- (void) settingsChanged {
444 [self showLeftButton:@"Respring" withStyle:2 rightButton:@"Cancel" withStyle:0];
445 settingsChanged = YES;
446}
447
448- (NSString *) title {
449 return @"WinterBoard";
450}
451
452- (void) setPreferenceValue:(id)value specifier:(PSSpecifier *)spec {
7c88a3f1 453 NSString *key([spec propertyForKey:@"key"]);
224d48e3
JF
454 if ([[spec propertyForKey:@"negate"] boolValue])
455 value = [NSNumber numberWithBool:(![value boolValue])];
7c88a3f1 456 [_settings setValue:value forKey:key];
224d48e3
JF
457 [self settingsChanged];
458}
459
460- (id) readPreferenceValue:(PSSpecifier *)spec {
461 NSString *key([spec propertyForKey:@"key"]);
462 id defaultValue([spec propertyForKey:@"default"]);
463 id plistValue([_settings objectForKey:key]);
464 if (!plistValue)
465 return defaultValue;
466 if ([[spec propertyForKey:@"negate"] boolValue])
467 plistValue = [NSNumber numberWithBool:(![plistValue boolValue])];
468 return plistValue;
469}
470
471@end