1 /* xscreensaver, Copyright (c) 2006-2012 Jamie Zawinski <jwz@jwz.org>
3 * Permission to use, copy, modify, distribute, and sell this software and its
4 * documentation for any purpose is hereby granted without fee, provided that
5 * the above copyright notice appear in all copies and that both that
6 * copyright notice and this permission notice appear in supporting
7 * documentation. No representations are made about the suitability of this
8 * software for any purpose. It is provided "as is" without express or
12 /* This implements the substrate of the xscreensaver preferences code:
13 It does this by writing defaults to, and reading values from, the
14 NSUserDefaultsController (and ScreenSaverDefaults/NSUserDefaults)
15 and thereby reading the preferences that may have been edited by
16 the UI (XScreenSaverConfigSheet).
20 # import <ScreenSaver/ScreenSaverDefaults.h>
23 #import "PrefsReader.h"
24 #import "screenhackI.h"
26 @implementation PrefsReader
28 /* Normally we read resources by looking up "KEY" in the database
29 "org.jwz.xscreensaver.SAVERNAME". But in the all-in-one iPhone
30 app, everything is stored in the database "org.jwz.xscreensaver"
31 instead, so transform keys to "SAVERNAME.KEY".
33 NOTE: This is duplicated in XScreenSaverConfigSheet.m, cause I suck.
35 - (NSString *) makeKey:(NSString *)key
38 NSString *prefix = [saver_name stringByAppendingString:@"."];
39 if (! [key hasPrefix:prefix]) // Don't double up!
40 key = [prefix stringByAppendingString:key];
45 - (NSString *) makeCKey:(const char *)key
47 return [self makeKey:[NSString stringWithCString:key
48 encoding:NSUTF8StringEncoding]];
52 /* Converts an array of "key:value" strings to an NSDictionary.
54 - (NSDictionary *) defaultsToDict: (const char * const *) defs
56 NSDictionary *dict = [NSMutableDictionary dictionaryWithCapacity:20];
58 char *line = strdup (*defs);
61 while (*key == '.' || *key == '*' || *key == ' ' || *key == '\t')
64 while (*val && *val != ':')
66 if (*val != ':') abort();
68 while (*val == ' ' || *val == '\t')
72 while (L > 0 && (val[L-1] == ' ' || val[L-1] == '\t'))
75 // When storing into preferences, look at the default string and
76 // decide whether it's a boolean, int, float, or string, and store
77 // an object of the appropriate type in the prefs.
79 NSString *nskey = [self makeCKey:key];
84 if (!strcasecmp (val, "true") || !strcasecmp (val, "yes"))
85 nsval = [NSNumber numberWithBool:YES];
86 else if (!strcasecmp (val, "false") || !strcasecmp (val, "no"))
87 nsval = [NSNumber numberWithBool:NO];
88 else if (1 == sscanf (val, " %d %c", &dd, &cc))
89 nsval = [NSNumber numberWithInt:dd];
90 else if (1 == sscanf (val, " %lf %c", &ff, &cc))
91 nsval = [NSNumber numberWithDouble:ff];
93 nsval = [NSString stringWithCString:val encoding:NSUTF8StringEncoding];
95 // NSLog (@"default: \"%@\" = \"%@\" [%@]", nskey, nsval, [nsval class]);
96 [dict setValue:nsval forKey:nskey];
104 /* Initialize the Cocoa preferences database:
105 - sets the default preferences values from the 'defaults' array;
106 - binds 'self' to each preference as an observer;
107 - ensures that nothing is mentioned in 'options' and not in 'defaults';
108 - ensures that nothing is mentioned in 'defaults' and not in 'options'.
110 - (void) registerXrmKeys: (const XrmOptionDescRec *) opts
111 defaults: (const char * const *) defs
113 // Store the contents of 'defaults' into the real preferences database.
114 NSDictionary *defsdict = [self defaultsToDict:defs];
115 [userDefaults registerDefaults:defsdict];
117 // Save a copy of the default options, since iOS doesn't have
118 // [userDefaultsController initialValues].
121 [defaultOptions release];
122 defaultOptions = [[NSMutableDictionary dictionaryWithCapacity:20]
124 for (NSString *key in defsdict) {
125 [defaultOptions setValue:[defsdict objectForKey:key] forKey:key];
129 userDefaultsController =
130 [[NSUserDefaultsController alloc] initWithDefaults:userDefaults
131 initialValues:defsdict];
133 userDefaultsController = userDefaults;
134 # endif // USE_IPHONE
136 NSDictionary *optsdict = [NSMutableDictionary dictionaryWithCapacity:20];
138 while (opts[0].option) {
139 //const char *option = opts->option;
140 const char *resource = opts->specifier;
142 while (*resource == '.' || *resource == '*')
144 NSString *nsresource = [self makeCKey:resource];
146 // make sure there's no resource mentioned in options and not defaults.
147 if (![defsdict objectForKey:nsresource]) {
148 if (! (!strcmp(resource, "font") || // don't warn about these
149 !strcmp(resource, "textLiteral") ||
150 !strcmp(resource, "textFile") ||
151 !strcmp(resource, "textURL") ||
152 !strcmp(resource, "textProgram") ||
153 !strcmp(resource, "imageDirectory")))
154 NSLog (@"warning: \"%s\" is in options but not defaults", resource);
156 [optsdict setValue:nsresource forKey:nsresource];
162 // make sure there's no resource mentioned in defaults and not options.
163 for (NSString *key in defsdict) {
164 if (! [optsdict objectForKey:key])
165 if (! ([key isEqualToString:@"foreground"] || // don't warn about these
166 [key isEqualToString:@"background"] ||
167 [key isEqualToString:@"Background"] ||
168 [key isEqualToString:@"geometry"] ||
169 [key isEqualToString:@"font"] ||
170 [key isEqualToString:@"dontClearRoot"] ||
173 [key isEqualToString:@"fpsSolid"] ||
174 [key isEqualToString:@"fpsTop"] ||
175 [key isEqualToString:@"titleFont"] ||
177 // analogtv.c settings
178 [key isEqualToString:@"TVBrightness"] ||
179 [key isEqualToString:@"TVColor"] ||
180 [key isEqualToString:@"TVContrast"] ||
181 [key isEqualToString:@"TVTint"]
183 NSLog (@"warning: \"%@\" is in defaults but not options", key);
188 // Dump the entire resource database.
189 NSDictionary *d = [userDefaults dictionaryRepresentation];
190 for (NSObject *key in [[d allKeys]
191 sortedArrayUsingSelector:@selector(compare:)]) {
192 NSObject *val = [d objectForKey:key];
193 NSLog (@"%@ = %@", key, val);
199 - (NSUserDefaultsController *) userDefaultsController
201 NSAssert(userDefaultsController, @"userDefaultsController uninitialized");
202 return userDefaultsController;
205 - (NSDictionary *) defaultOptions
207 NSAssert(defaultOptions, @"userDefaultsController uninitialized");
208 return defaultOptions;
212 - (NSObject *) getObjectResource: (const char *) name
215 NSString *key = [self makeCKey:name];
216 NSObject *obj = [userDefaults objectForKey:key];
220 // If key is "foo.bar.baz", check "foo.bar.baz", "bar.baz", and "baz".
222 const char *dot = strchr (name, '.');
231 - (char *) getStringResource: (const char *) name
233 NSObject *o = [self getObjectResource:name];
234 //NSLog(@"%s = %@",name,o);
236 if (! (!strcmp(name, "eraseMode") || // erase.c
237 // xlockmore.c reads all of these whether used or not...
238 !strcmp(name, "right3d") ||
239 !strcmp(name, "left3d") ||
240 !strcmp(name, "both3d") ||
241 !strcmp(name, "none3d") ||
242 !strcmp(name, "font") ||
243 !strcmp(name, "labelFont") || // grabclient.c
244 !strcmp(name, "titleFont") ||
245 !strcmp(name, "fpsFont") || // fps.c
246 !strcmp(name, "foreground") || // fps.c
247 !strcmp(name, "background") ||
248 !strcmp(name, "textLiteral")
250 NSLog(@"warning: no preference \"%s\" [string]", name);
253 if (! [o isKindOfClass:[NSString class]]) {
254 NSLog(@"asked for %s as a string, but it is a %@", name, [o class]);
255 o = [(NSNumber *) o stringValue];
258 NSString *os = (NSString *) o;
259 char *result = strdup ([os cStringUsingEncoding:NSUTF8StringEncoding]);
261 // Kludge: if the string is surrounded with single-quotes, remove them.
262 // This happens when the .xml file says things like arg="-foo 'bar baz'"
263 if (result[0] == '\'' && result[strlen(result)-1] == '\'') {
264 result[strlen(result)-1] = 0;
265 strcpy (result, result+1);
268 // Kludge: assume that any string that begins with "~" and has a "/"
269 // anywhere in it should be expanded as if it is a pathname.
270 if (result[0] == '~' && strchr (result, '/')) {
271 os = [NSString stringWithCString:result encoding:NSUTF8StringEncoding];
273 result = strdup ([[os stringByExpandingTildeInPath]
274 cStringUsingEncoding:NSUTF8StringEncoding]);
281 - (double) getFloatResource: (const char *) name
283 NSObject *o = [self getObjectResource:name];
285 // xlockmore.c reads all of these whether used or not...
286 if (! (!strcmp(name, "cycles") ||
287 !strcmp(name, "size") ||
288 !strcmp(name, "use3d") ||
289 !strcmp(name, "delta3d") ||
290 !strcmp(name, "wireframe") ||
291 !strcmp(name, "showFPS") ||
292 !strcmp(name, "fpsSolid") ||
293 !strcmp(name, "fpsTop") ||
294 !strcmp(name, "mono") ||
295 !strcmp(name, "count") ||
296 !strcmp(name, "ncolors") ||
297 !strcmp(name, "doFPS") || // fps.c
298 !strcmp(name, "eraseSeconds") // erase.c
300 NSLog(@"warning: no preference \"%s\" [float]", name);
303 if ([o isKindOfClass:[NSString class]]) {
304 return [(NSString *) o doubleValue];
305 } else if ([o isKindOfClass:[NSNumber class]]) {
306 return [(NSNumber *) o doubleValue];
308 NSAssert2(0, @"%s = \"%@\" but should have been an NSNumber", name, o);
314 - (int) getIntegerResource: (const char *) name
316 // Sliders might store float values for integral resources; round them.
317 float v = [self getFloatResource:name];
318 int i = (int) (v + (v < 0 ? -0.5 : 0.5)); // ignore sign or -1 rounds to 0
319 // if (i != v) NSLog(@"%s: rounded %.3f to %d", name, v, i);
324 - (BOOL) getBooleanResource: (const char *) name
326 NSObject *o = [self getObjectResource:name];
329 } else if ([o isKindOfClass:[NSNumber class]]) {
330 double n = [(NSNumber *) o doubleValue];
331 if (n == 0) return NO;
332 else if (n == 1) return YES;
334 } else if ([o isKindOfClass:[NSString class]]) {
335 NSString *s = (NSString *) o;
336 if ([s isEqualToString:@"true"] ||
337 [s isEqualToString:@"yes"] ||
338 [s isEqualToString:@"1"])
340 else if ([s isEqualToString:@"false"] ||
341 [s isEqualToString:@"no"] ||
342 [s isEqualToString:@"0"] ||
343 [s isEqualToString:@""])
349 NSAssert2(0, @"%s = \"%@\" but should have been a boolean", name, o);
355 - (id) initWithName: (NSString *) name
356 xrmKeys: (const XrmOptionDescRec *) opts
357 defaults: (const char * const *) defs
360 if (!self) return nil;
363 userDefaults = [ScreenSaverDefaults defaultsForModuleWithName:name];
365 userDefaults = [NSUserDefaults standardUserDefaults];
366 # endif // USE_IPHONE
368 // Convert "org.jwz.xscreensaver.NAME" to just "NAME".
369 NSRange r = [name rangeOfString:@"." options:NSBackwardsSearch];
371 name = [name substringFromIndex:r.location+1];
372 name = [name stringByReplacingOccurrencesOfString:@" " withString:@""];
373 saver_name = [name retain];
375 [self registerXrmKeys:opts defaults:defs];
381 [saver_name release];
382 [userDefaultsController release];