]> git.saurik.com Git - winterboard.git/blob - Settings.mm
Add a setting to hide the WinterBoard icon.
[winterboard.git] / Settings.mm
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
45 #include <dlfcn.h>
46
47 static BOOL (*IsIconHiddenDisplayId)(NSString *);
48 static BOOL (*HideIconViaDisplayId)(NSString *);
49 static BOOL (*UnHideIconViaDisplayId)(NSString *);
50
51 static const NSString *WinterBoardDisplayID = @"com.saurik.WinterBoard";
52
53 extern NSString *PSTableCellKey;
54 extern "C" UIImage *_UIImageWithName(NSString *);
55
56 static UIImage *checkImage;
57 static UIImage *uncheckedImage;
58
59 static BOOL settingsChanged;
60 static NSMutableDictionary *_settings;
61 static NSString *_plist;
62
63 /* Theme Settings Controller {{{ */
64 @interface WBSThemesController: PSViewController <UITableViewDelegate, UITableViewDataSource> {
65 UITableView *_tableView;
66 NSMutableArray *_themes;
67 }
68
69 @property (nonatomic, retain) NSMutableArray *themes;
70
71 + (void) load;
72
73 - (id) initForContentSize:(CGSize)size;
74 - (id) view;
75 - (id) navigationTitle;
76 - (void) themesChanged;
77
78 - (int) numberOfSectionsInTableView:(UITableView *)tableView;
79 - (id) tableView:(UITableView *)tableView titleForHeaderInSection:(int)section;
80 - (int) tableView:(UITableView *)tableView numberOfRowsInSection:(int)section;
81 - (id) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
82 - (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath;
83 - (void) tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath;
84 - (UITableViewCellEditingStyle) tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath;
85 - (BOOL) tableView:(UITableView *)tableView shouldIndentWhileEditingRowAtIndexPath:(NSIndexPath *)indexPath;
86 - (BOOL) tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath;
87
88 @end
89
90 @implementation WBSThemesController
91
92 @synthesize themes = _themes;
93
94 + (void) load {
95 NSAutoreleasePool *pool([[NSAutoreleasePool alloc] init]);
96 checkImage = [_UIImageWithName(@"UIPreferencesBlueCheck.png") retain];
97 uncheckedImage = [[UIImage imageWithContentsOfFile:@"/System/Library/PreferenceBundles/WinterBoardSettings.bundle/SearchResultsCheckmarkClear.png"] retain];
98 [pool release];
99 }
100
101 - (id) initForContentSize:(CGSize)size {
102 if ((self = [super initForContentSize:size]) != nil) {
103 self.themes = [_settings objectForKey:@"Themes"];
104 if (!_themes) {
105 if (NSString *theme = [_settings objectForKey:@"Theme"]) {
106 self.themes = [NSMutableArray arrayWithObject:
107 [NSMutableDictionary dictionaryWithObjectsAndKeys:
108 theme, @"Name",
109 [NSNumber numberWithBool:YES], @"Active", nil]];
110 [_settings removeObjectForKey:@"Theme"];
111 }
112 if (!_themes)
113 self.themes = [NSMutableArray array];
114 [_settings setObject:_themes forKey:@"Themes"];
115 }
116
117 NSMutableArray *themesOnDisk([NSMutableArray array]);
118
119 [themesOnDisk
120 addObjectsFromArray:[[NSFileManager defaultManager]
121 contentsOfDirectoryAtPath:@"/Library/Themes" error:NULL]
122 ];
123
124 [themesOnDisk addObjectsFromArray:[[NSFileManager defaultManager]
125 contentsOfDirectoryAtPath:[NSString stringWithFormat:@"%@/Library/SummerBoard/Themes", NSHomeDirectory()]
126 error:NULL
127 ]];
128
129 for (int i = 0, count = [themesOnDisk count]; i < count; i++) {
130 NSString *theme = [themesOnDisk objectAtIndex:i];
131 if ([theme hasSuffix:@".theme"])
132 [themesOnDisk replaceObjectAtIndex:i withObject:[theme stringByDeletingPathExtension]];
133 }
134
135 NSMutableSet *themesSet([NSMutableSet set]);
136
137 for (int i = 0, count = [_themes count]; i < count; i++) {
138 NSDictionary *theme([_themes objectAtIndex:i]);
139 NSString *name([theme objectForKey:@"Name"]);
140
141 if (!name || ![themesOnDisk containsObject:name]) {
142 [_themes removeObjectAtIndex:i];
143 i--;
144 count--;
145 } else {
146 [themesSet addObject:name];
147 }
148 }
149
150 for (NSString *theme in themesOnDisk) {
151 if ([themesSet containsObject:theme])
152 continue;
153 [themesSet addObject:theme];
154
155 [_themes insertObject:[NSMutableDictionary dictionaryWithObjectsAndKeys:
156 theme, @"Name",
157 [NSNumber numberWithBool:NO], @"Active",
158 nil] atIndex:0];
159 }
160
161 _tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, 320, 480-64) style:UITableViewStyleGrouped];
162 [_tableView setDataSource:self];
163 [_tableView setDelegate:self];
164 [_tableView setEditing:YES];
165 [_tableView setAllowsSelectionDuringEditing:YES];
166 [self showLeftButton:@"WinterBoard" withStyle:1 rightButton:nil withStyle:0];
167 }
168 return self;
169 }
170
171 - (void) dealloc {
172 [_tableView release];
173 [_themes release];
174 [super dealloc];
175 }
176
177 - (id) navigationTitle {
178 return @"Themes";
179 }
180
181 - (id) view {
182 return _tableView;
183 }
184
185 - (void) themesChanged {
186 settingsChanged = YES;
187 }
188
189 /* UITableViewDelegate / UITableViewDataSource Methods {{{ */
190 - (int) numberOfSectionsInTableView:(UITableView *)tableView {
191 return 1;
192 }
193
194 - (id) tableView:(UITableView *)tableView titleForHeaderInSection:(int)section {
195 return nil;
196 }
197
198 - (int) tableView:(UITableView *)tableView numberOfRowsInSection:(int)section {
199 return _themes.count;
200 }
201
202 - (id) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
203 UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"ThemeCell"];
204 if (!cell) {
205 cell = [[[UITableViewCell alloc] initWithFrame:CGRectMake(0, 0, 100, 100) reuseIdentifier:@"ThemeCell"] autorelease];
206 //[cell setTableViewStyle:UITableViewCellStyleDefault];
207 }
208
209 NSDictionary *theme([_themes objectAtIndex:indexPath.row]);
210 cell.text = [theme objectForKey:@"Name"];
211 cell.hidesAccessoryWhenEditing = NO;
212 NSNumber *active([theme objectForKey:@"Active"]);
213 BOOL inactive(active == nil || ![active boolValue]);
214 [cell setImage:(inactive ? uncheckedImage : checkImage)];
215 return cell;
216 }
217
218 - (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
219 UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
220 NSMutableDictionary *theme = [_themes objectAtIndex:indexPath.row];
221 NSNumber *active = [theme objectForKey:@"Active"];
222 BOOL inactive = active == nil || ![active boolValue];
223 [theme setObject:[NSNumber numberWithBool:inactive] forKey:@"Active"];
224 [cell setImage:(!inactive ? uncheckedImage : checkImage)];
225 [tableView deselectRowAtIndexPath:(NSIndexPath *)indexPath animated:YES];
226 [self themesChanged];
227 }
228
229 - (void) tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath {
230 NSUInteger fromIndex = [fromIndexPath row];
231 NSUInteger toIndex = [toIndexPath row];
232 if (fromIndex == toIndex)
233 return;
234 NSMutableDictionary *theme = [[[_themes objectAtIndex:fromIndex] retain] autorelease];
235 [_themes removeObjectAtIndex:fromIndex];
236 [_themes insertObject:theme atIndex:toIndex];
237 [self themesChanged];
238 }
239
240 - (UITableViewCellEditingStyle) tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath {
241 return UITableViewCellEditingStyleNone;
242 }
243
244 - (BOOL) tableView:(UITableView *)tableView shouldIndentWhileEditingRowAtIndexPath:(NSIndexPath *)indexPath {
245 return NO;
246 }
247
248 - (BOOL) tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath {
249 return YES;
250 }
251 /* }}} */
252 @end
253 /* }}} */
254
255 @interface WBSettingsController: PSListController {
256 }
257
258 - (id) initForContentSize:(CGSize)size;
259 - (void) dealloc;
260 - (void) suspend;
261 - (void) navigationBarButtonClicked:(int)buttonIndex;
262 - (void) viewWillRedisplay;
263 - (void) pushController:(id)controller;
264 - (id) specifiers;
265 - (void) settingsChanged;
266 - (NSString *) title;
267 - (void) setPreferenceValue:(id)value specifier:(PSSpecifier *)spec;
268 - (id) readPreferenceValue:(PSSpecifier *)spec;
269
270 @end
271
272 @implementation WBSettingsController
273
274 + (void) load {
275 NSLog(@"LH");
276 void *libhide(dlopen("/usr/lib/hide.dylib", RTLD_LAZY));
277 NSLog(@"LH:%x", libhide);
278 IsIconHiddenDisplayId = reinterpret_cast<BOOL (*)(NSString *)>(dlsym(libhide, "IsIconHiddenDisplayId"));
279 NSLog(@"LH:%x", IsIconHiddenDisplayId);
280 HideIconViaDisplayId = reinterpret_cast<BOOL (*)(NSString *)>(dlsym(libhide, "HideIconViaDisplayId"));
281 NSLog(@"LH:%x", HideIconViaDisplayId);
282 UnHideIconViaDisplayId = reinterpret_cast<BOOL (*)(NSString *)>(dlsym(libhide, "UnHideIconViaDisplayId"));
283 NSLog(@"LH:%x", UnHideIconViaDisplayId);
284 }
285
286 - (id) initForContentSize:(CGSize)size {
287 if ((self = [super initForContentSize:size]) != nil) {
288 _plist = [[NSString stringWithFormat:@"%@/Library/Preferences/com.saurik.WinterBoard.plist", NSHomeDirectory()] retain];
289 _settings = [([NSMutableDictionary dictionaryWithContentsOfFile:_plist] ?: [NSMutableDictionary dictionary]) retain];
290
291 [_settings setObject:[NSNumber numberWithBool:IsIconHiddenDisplayId(WinterBoardDisplayID)] forKey:@"IconHidden"];
292 } return self;
293 }
294
295 - (void) dealloc {
296 [_settings release];
297 [_plist release];
298 [super dealloc];
299 }
300
301 - (void) suspend {
302 if (!settingsChanged)
303 return;
304
305 NSData *data([NSPropertyListSerialization dataFromPropertyList:_settings format:NSPropertyListBinaryFormat_v1_0 errorDescription:NULL]);
306 if (!data)
307 return;
308 if (![data writeToFile:_plist options:NSAtomicWrite error:NULL])
309 return;
310
311 ([[_settings objectForKey:@"IconHidden"] boolValue] ? HideIconViaDisplayId : UnHideIconViaDisplayId)(WinterBoardDisplayID);
312
313 unlink("/User/Library/Caches/com.apple.springboard-imagecache-icons");
314 unlink("/User/Library/Caches/com.apple.springboard-imagecache-icons.plist");
315 unlink("/User/Library/Caches/com.apple.springboard-imagecache-smallicons");
316 unlink("/User/Library/Caches/com.apple.springboard-imagecache-smallicons.plist");
317
318 system("rm -rf /User/Library/Caches/SpringBoardIconCache");
319 system("rm -rf /User/Library/Caches/SpringBoardIconCache-small");
320
321 system("killall SpringBoard");
322 }
323
324 - (void) navigationBarButtonClicked:(int)buttonIndex {
325 if (!settingsChanged) {
326 [super navigationBarButtonClicked:buttonIndex];
327 return;
328 }
329
330 if (buttonIndex == 0)
331 settingsChanged = NO;
332
333 [self suspend];
334 [self.rootController popController];
335 }
336
337 - (void) viewWillRedisplay {
338 if (settingsChanged)
339 [self settingsChanged];
340 [super viewWillRedisplay];
341 }
342
343 - (void) pushController:(id)controller {
344 [self hideNavigationBarButtons];
345 [super pushController:controller];
346 }
347
348 - (id) specifiers {
349 if (!_specifiers)
350 _specifiers = [[self loadSpecifiersFromPlistName:@"WinterBoard" target:self] retain];
351 return _specifiers;
352 }
353
354 - (void) settingsChanged {
355 [self showLeftButton:@"Respring" withStyle:2 rightButton:@"Cancel" withStyle:0];
356 settingsChanged = YES;
357 }
358
359 - (NSString *) title {
360 return @"WinterBoard";
361 }
362
363 - (void) setPreferenceValue:(id)value specifier:(PSSpecifier *)spec {
364 NSString *key([spec propertyForKey:@"key"]);
365 if ([[spec propertyForKey:@"negate"] boolValue])
366 value = [NSNumber numberWithBool:(![value boolValue])];
367 [_settings setValue:value forKey:key];
368 [self settingsChanged];
369 }
370
371 - (id) readPreferenceValue:(PSSpecifier *)spec {
372 NSString *key([spec propertyForKey:@"key"]);
373 id defaultValue([spec propertyForKey:@"default"]);
374 id plistValue([_settings objectForKey:key]);
375 if (!plistValue)
376 return defaultValue;
377 if ([[spec propertyForKey:@"negate"] boolValue])
378 plistValue = [NSNumber numberWithBool:(![plistValue boolValue])];
379 return plistValue;
380 }
381
382 @end