1 /* xscreensaver, Copyright (c) 2006-2013 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"
25 #import "screenhackI.h"
30 /* GlobalDefaults is an NSUserDefaults implementation that writes into
31 the preferences key we provide, instead of whatever the default would
32 be for this app. We do this by invoking the Core Foundation preferences
33 routines directly, while presenting the same API as NSUserDefaults.
35 We need this so that global prefs will go into the file
36 Library/Preferences/org.jwz.xscreensaver.updater.plist instead of into
37 Library/Preferences/ByHost/org.jwz.xscreensaver.Maze.XXXXX.plist
38 with the per-saver prefs.
40 The ScreenSaverDefaults class *almost* does this, but it always writes
41 into the ByHost subdirectory, which means it's not readable by an app
42 that tries to access it with a plain old +standardUserDefaults.
44 @interface GlobalDefaults : NSUserDefaults
47 NSDictionary *defaults;
51 @implementation GlobalDefaults
52 - (id) initWithDomain:(NSString *)_domain
55 domain = [_domain retain];
66 - (void)registerDefaults:(NSDictionary *)dict
68 defaults = [dict retain];
71 - (id)objectForKey:(NSString *)key
73 NSObject *obj = (NSObject *)
74 CFPreferencesCopyAppValue ((CFStringRef) key, (CFStringRef) domain);
76 obj = [defaults objectForKey:key];
80 - (void)setObject:(id)value forKey:(NSString *)key
82 if (value && defaults) {
83 // If the value is the default, then remove it instead.
84 NSObject *def = [defaults objectForKey:key];
85 if (def && [def isEqual:value])
88 CFPreferencesSetAppValue ((CFStringRef) key,
89 (CFPropertyListRef) value,
90 (CFStringRef) domain);
96 return CFPreferencesAppSynchronize ((CFStringRef) domain);
100 // Make sure these all call our objectForKey.
101 // Might not be necessary, but safe.
103 - (NSString *)stringForKey:(NSString *)key
105 return [[self objectForKey:key] stringValue];
108 - (NSArray *)arrayForKey:(NSString *)key
110 return (NSArray *) [self objectForKey:key];
113 - (NSDictionary *)dictionaryForKey:(NSString *)key
115 return (NSDictionary *) [self objectForKey:key];
118 - (NSData *)dataForKey:(NSString *)key
120 return (NSData *) [self objectForKey:key];
123 - (NSArray *)stringArrayForKey:(NSString *)key
125 return (NSArray *) [self objectForKey:key];
128 - (NSInteger)integerForKey:(NSString *)key
130 return [[self objectForKey:key] integerValue];
133 - (float)floatForKey:(NSString *)key
135 return [[self objectForKey:key] floatValue];
138 - (double)doubleForKey:(NSString *)key
140 return [[self objectForKey:key] doubleValue];
143 - (BOOL)boolForKey:(NSString *)key
145 return [[self objectForKey:key] integerValue];
148 // Make sure these all call our setObject.
149 // Might not be necessary, but safe.
151 - (void)removeObjectForKey:(NSString *)key
153 [self setObject:NULL forKey:key];
156 - (void)setInteger:(NSInteger)value forKey:(NSString *)key
158 [self setObject:[NSNumber numberWithInteger:value] forKey:key];
161 - (void)setFloat:(float)value forKey:(NSString *)key
163 [self setObject:[NSNumber numberWithFloat:value] forKey:key];
166 - (void)setDouble:(double)value forKey:(NSString *)key
168 [self setObject:[NSNumber numberWithDouble:value] forKey:key];
171 - (void)setBool:(BOOL)value forKey:(NSString *)key
173 [self setObject:[NSNumber numberWithBool:value] forKey:key];
178 #endif // !USE_IPHONE
181 @implementation PrefsReader
183 /* Normally we read resources by looking up "KEY" in the database
184 "org.jwz.xscreensaver.SAVERNAME". But in the all-in-one iPhone
185 app, everything is stored in the database "org.jwz.xscreensaver"
186 instead, so transform keys to "SAVERNAME.KEY".
188 NOTE: This is duplicated in XScreenSaverConfigSheet.m, cause I suck.
190 - (NSString *) makeKey:(NSString *)key
193 NSString *prefix = [saver_name stringByAppendingString:@"."];
194 if (! [key hasPrefix:prefix]) // Don't double up!
195 key = [prefix stringByAppendingString:key];
200 - (NSString *) makeCKey:(const char *)key
202 return [self makeKey:[NSString stringWithCString:key
203 encoding:NSUTF8StringEncoding]];
207 /* Converts an array of "key:value" strings to an NSDictionary.
209 - (NSDictionary *) defaultsToDict: (const char * const *) defs
211 NSDictionary *dict = [NSMutableDictionary dictionaryWithCapacity:20];
213 char *line = strdup (*defs);
216 while (*key == '.' || *key == '*' || *key == ' ' || *key == '\t')
219 while (*val && *val != ':')
221 if (*val != ':') abort();
223 while (*val == ' ' || *val == '\t')
227 while (L > 0 && (val[L-1] == ' ' || val[L-1] == '\t'))
230 // When storing into preferences, look at the default string and
231 // decide whether it's a boolean, int, float, or string, and store
232 // an object of the appropriate type in the prefs.
234 NSString *nskey = [self makeCKey:key];
239 if (!strcasecmp (val, "true") || !strcasecmp (val, "yes"))
240 nsval = [NSNumber numberWithBool:YES];
241 else if (!strcasecmp (val, "false") || !strcasecmp (val, "no"))
242 nsval = [NSNumber numberWithBool:NO];
243 else if (1 == sscanf (val, " %d %c", &dd, &cc))
244 nsval = [NSNumber numberWithInt:dd];
245 else if (1 == sscanf (val, " %lf %c", &ff, &cc))
246 nsval = [NSNumber numberWithDouble:ff];
248 nsval = [NSString stringWithCString:val encoding:NSUTF8StringEncoding];
250 // NSLog (@"default: \"%@\" = \"%@\" [%@]", nskey, nsval, [nsval class]);
251 [dict setValue:nsval forKey:nskey];
259 /* Initialize the Cocoa preferences database:
260 - sets the default preferences values from the 'defaults' array;
261 - binds 'self' to each preference as an observer;
262 - ensures that nothing is mentioned in 'options' and not in 'defaults';
263 - ensures that nothing is mentioned in 'defaults' and not in 'options'.
265 - (void) registerXrmKeys: (const XrmOptionDescRec *) opts
266 defaults: (const char * const *) defs
268 // Store the contents of 'defaults' into the real preferences database.
269 NSDictionary *defsdict = [self defaultsToDict:defs];
270 [userDefaults registerDefaults:defsdict];
271 [globalDefaults registerDefaults:UPDATER_DEFAULTS];
273 // Save a copy of the default options, since iOS doesn't have
274 // [userDefaultsController initialValues].
277 [defaultOptions release];
278 defaultOptions = [[NSMutableDictionary dictionaryWithCapacity:20]
280 for (NSString *key in defsdict) {
281 [defaultOptions setValue:[defsdict objectForKey:key] forKey:key];
285 userDefaultsController =
286 [[NSUserDefaultsController alloc] initWithDefaults:userDefaults
287 initialValues:defsdict];
288 globalDefaultsController =
289 [[NSUserDefaultsController alloc] initWithDefaults:globalDefaults
290 initialValues:defsdict];
292 userDefaultsController = userDefaults;
293 globalDefaultsController = userDefaults;
294 # endif // USE_IPHONE
296 NSDictionary *optsdict = [NSMutableDictionary dictionaryWithCapacity:20];
298 while (opts[0].option) {
299 //const char *option = opts->option;
300 const char *resource = opts->specifier;
302 while (*resource == '.' || *resource == '*')
304 NSString *nsresource = [self makeCKey:resource];
306 // make sure there's no resource mentioned in options and not defaults.
307 if (![defsdict objectForKey:nsresource]) {
308 if (! (!strcmp(resource, "font") || // don't warn about these
309 !strcmp(resource, "foreground") ||
310 !strcmp(resource, "textLiteral") ||
311 !strcmp(resource, "textFile") ||
312 !strcmp(resource, "textURL") ||
313 !strcmp(resource, "textProgram") ||
314 !strcmp(resource, "imageDirectory")))
315 NSLog (@"warning: \"%s\" is in options but not defaults", resource);
317 [optsdict setValue:nsresource forKey:nsresource];
323 // make sure there's no resource mentioned in defaults and not options.
324 for (NSString *key in defsdict) {
325 if (! [optsdict objectForKey:key])
326 if (! ([key isEqualToString:@"foreground"] || // don't warn about these
327 [key isEqualToString:@"background"] ||
328 [key isEqualToString:@"Background"] ||
329 [key isEqualToString:@"geometry"] ||
330 [key isEqualToString:@"font"] ||
331 [key isEqualToString:@"dontClearRoot"] ||
334 [key isEqualToString:@"fpsSolid"] ||
335 [key isEqualToString:@"fpsTop"] ||
336 [key isEqualToString:@"titleFont"] ||
338 // analogtv.c settings
339 [key isEqualToString:@"TVBrightness"] ||
340 [key isEqualToString:@"TVColor"] ||
341 [key isEqualToString:@"TVContrast"] ||
342 [key isEqualToString:@"TVTint"]
344 NSLog (@"warning: \"%@\" is in defaults but not options", key);
349 // Dump the entire resource database.
350 NSLog(@"userDefaults:");
351 NSDictionary *d = [userDefaults dictionaryRepresentation];
352 for (NSObject *key in [[d allKeys]
353 sortedArrayUsingSelector:@selector(compare:)]) {
354 NSObject *val = [d objectForKey:key];
355 NSLog (@"%@ = %@", key, val);
357 NSLog(@"globalDefaults:");
358 d = [globalDefaults dictionaryRepresentation];
359 for (NSObject *key in [[d allKeys]
360 sortedArrayUsingSelector:@selector(compare:)]) {
361 NSObject *val = [d objectForKey:key];
362 NSLog (@"%@ = %@", key, val);
368 - (NSUserDefaultsController *) userDefaultsController
370 NSAssert(userDefaultsController, @"userDefaultsController uninitialized");
371 return userDefaultsController;
374 - (NSUserDefaultsController *) globalDefaultsController
376 NSAssert(globalDefaultsController, @"globalDefaultsController uninitialized");
377 return globalDefaultsController;
380 - (NSDictionary *) defaultOptions
382 NSAssert(defaultOptions, @"defaultOptions uninitialized");
383 return defaultOptions;
387 - (NSObject *) getObjectResource: (const char *) name
389 // First look in userDefaults, then in globalDefaults.
390 for (int globalp = 0; globalp <= 1; globalp++) {
391 const char *name2 = name;
393 NSString *key = [self makeCKey:name2];
394 NSObject *obj = [(globalp ? globalDefaults : userDefaults)
399 // If key is "foo.bar.baz", check "foo.bar.baz", "bar.baz", and "baz".
401 const char *dot = strchr (name2, '.');
412 - (char *) getStringResource: (const char *) name
414 NSObject *o = [self getObjectResource:name];
415 //NSLog(@"%s = %@",name,o);
417 if (! (!strcmp(name, "eraseMode") || // erase.c
418 // xlockmore.c reads all of these whether used or not...
419 !strcmp(name, "right3d") ||
420 !strcmp(name, "left3d") ||
421 !strcmp(name, "both3d") ||
422 !strcmp(name, "none3d") ||
423 !strcmp(name, "font") ||
424 !strcmp(name, "labelFont") || // grabclient.c
425 !strcmp(name, "titleFont") ||
426 !strcmp(name, "fpsFont") || // fps.c
427 !strcmp(name, "foreground") || // fps.c
428 !strcmp(name, "background") ||
429 !strcmp(name, "textLiteral")
431 NSLog(@"warning: no preference \"%s\" [string]", name);
434 if (! [o isKindOfClass:[NSString class]]) {
435 NSLog(@"asked for %s as a string, but it is a %@", name, [o class]);
436 o = [(NSNumber *) o stringValue];
439 NSString *os = (NSString *) o;
440 char *result = strdup ([os cStringUsingEncoding:NSUTF8StringEncoding]);
442 // Kludge: if the string is surrounded with single-quotes, remove them.
443 // This happens when the .xml file says things like arg="-foo 'bar baz'"
444 if (result[0] == '\'' && result[strlen(result)-1] == '\'') {
445 result[strlen(result)-1] = 0;
446 strcpy (result, result+1);
449 // Kludge: assume that any string that begins with "~" and has a "/"
450 // anywhere in it should be expanded as if it is a pathname.
451 if (result[0] == '~' && strchr (result, '/')) {
452 os = [NSString stringWithCString:result encoding:NSUTF8StringEncoding];
454 result = strdup ([[os stringByExpandingTildeInPath]
455 cStringUsingEncoding:NSUTF8StringEncoding]);
462 - (double) getFloatResource: (const char *) name
464 NSObject *o = [self getObjectResource:name];
466 // xlockmore.c reads all of these whether used or not...
467 if (! (!strcmp(name, "cycles") ||
468 !strcmp(name, "size") ||
469 !strcmp(name, "use3d") ||
470 !strcmp(name, "delta3d") ||
471 !strcmp(name, "wireframe") ||
472 !strcmp(name, "showFPS") ||
473 !strcmp(name, "fpsSolid") ||
474 !strcmp(name, "fpsTop") ||
475 !strcmp(name, "mono") ||
476 !strcmp(name, "count") ||
477 !strcmp(name, "ncolors") ||
478 !strcmp(name, "doFPS") || // fps.c
479 !strcmp(name, "eraseSeconds") // erase.c
481 NSLog(@"warning: no preference \"%s\" [float]", name);
484 if ([o isKindOfClass:[NSString class]]) {
485 return [(NSString *) o doubleValue];
486 } else if ([o isKindOfClass:[NSNumber class]]) {
487 return [(NSNumber *) o doubleValue];
489 NSAssert2(0, @"%s = \"%@\" but should have been an NSNumber", name, o);
495 - (int) getIntegerResource: (const char *) name
497 // Sliders might store float values for integral resources; round them.
498 float v = [self getFloatResource:name];
499 int i = (int) (v + (v < 0 ? -0.5 : 0.5)); // ignore sign or -1 rounds to 0
500 // if (i != v) NSLog(@"%s: rounded %.3f to %d", name, v, i);
505 - (BOOL) getBooleanResource: (const char *) name
507 NSObject *o = [self getObjectResource:name];
510 } else if ([o isKindOfClass:[NSNumber class]]) {
511 double n = [(NSNumber *) o doubleValue];
512 if (n == 0) return NO;
513 else if (n == 1) return YES;
515 } else if ([o isKindOfClass:[NSString class]]) {
516 NSString *s = [((NSString *) o) lowercaseString];
517 if ([s isEqualToString:@"true"] ||
518 [s isEqualToString:@"yes"] ||
519 [s isEqualToString:@"1"])
521 else if ([s isEqualToString:@"false"] ||
522 [s isEqualToString:@"no"] ||
523 [s isEqualToString:@"0"] ||
524 [s isEqualToString:@""])
530 NSAssert2(0, @"%s = \"%@\" but should have been a boolean", name, o);
536 - (id) initWithName: (NSString *) name
537 xrmKeys: (const XrmOptionDescRec *) opts
538 defaults: (const char * const *) defs
541 if (!self) return nil;
544 userDefaults = [ScreenSaverDefaults defaultsForModuleWithName:name];
545 globalDefaults = [[[GlobalDefaults alloc] initWithDomain:@UPDATER_DOMAIN]
548 userDefaults = [NSUserDefaults standardUserDefaults];
549 globalDefaults = userDefaults;
550 # endif // USE_IPHONE
552 // Convert "org.jwz.xscreensaver.NAME" to just "NAME".
553 NSRange r = [name rangeOfString:@"." options:NSBackwardsSearch];
555 name = [name substringFromIndex:r.location+1];
556 name = [name stringByReplacingOccurrencesOfString:@" " withString:@""];
557 saver_name = [name retain];
559 [self registerXrmKeys:opts defaults:defs];
565 [saver_name release];
566 [userDefaultsController release];
567 [globalDefaultsController release];