X-Git-Url: http://git.hungrycats.org/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=OSX%2FPrefsReader.m;h=a1c4ed48e0625dc1f69aaa0f8cf4fad3a11e407c;hb=aa75c7476aeaa84cf3abc192b376a8b03c325213;hp=97abe2941bc286015d22c616948ce0c886d6dd55;hpb=b81f521c5ad7022ac12db18ca8fcdd9fb063831e;p=xscreensaver diff --git a/OSX/PrefsReader.m b/OSX/PrefsReader.m index 97abe294..a1c4ed48 100644 --- a/OSX/PrefsReader.m +++ b/OSX/PrefsReader.m @@ -1,4 +1,4 @@ -/* xscreensaver, Copyright (c) 2006-2012 Jamie Zawinski +/* xscreensaver, Copyright (c) 2006-2015 Jamie Zawinski * * Permission to use, copy, modify, distribute, and sell this software and its * documentation for any purpose is hereby granted without fee, provided that @@ -21,8 +21,190 @@ #endif #import "PrefsReader.h" +#import "Updater.h" #import "screenhackI.h" +#ifndef USE_IPHONE + +#include + +/* GlobalDefaults is an NSUserDefaults implementation that writes into + the preferences key we provide, instead of whatever the default would + be for this app. We do this by invoking the Core Foundation preferences + routines directly, while presenting the same API as NSUserDefaults. + + We need this so that global prefs will go into the file + Library/Preferences/org.jwz.xscreensaver.updater.plist instead of into + Library/Preferences/ByHost/org.jwz.xscreensaver.Maze.XXXXX.plist + with the per-saver prefs. + + The ScreenSaverDefaults class *almost* does this, but it always writes + into the ByHost subdirectory, which means it's not readable by an app + that tries to access it with a plain old +standardUserDefaults. + */ +@interface GlobalDefaults : NSUserDefaults +{ + NSString *domain; + NSDictionary *defaults; +} +@end + +@implementation GlobalDefaults +- (id) initWithDomain:(NSString *)_domain module:(NSString *)_module +{ + // Key-Value Observing tries to create an Objective-C class named + // NSKVONotifying_GlobalDefaults when the configuration page is shown. But if + // this is the second XScreenSaver .saver running in the same process, class + // creation fails because that class name was already used by the first + // .saver, and it refers to the GlobalDefaults from the other .saver. + + // This gives the class a unique name, sidestepping the above issue. + + // It really just needs to be unique for this .saver and this instance. + // Using the pointer to the .saver's mach_header and the full path to the + // .saver would be preferable, but this should be good enough. + char class_name[128]; + sprintf(class_name, "GlobalDefaults_%s_%p_%u", + strrchr(_module.UTF8String, '.') + 1, self, random()); + Class c = objc_allocateClassPair([GlobalDefaults class], class_name, 0); + if (!c) + return nil; + objc_registerClassPair(c); + + self = [super init]; + object_setClass(self, c); + domain = [_domain retain]; + return self; +} + +- (void) dealloc +{ + Class c = object_getClass(self); + + [domain release]; + [defaults release]; + [super dealloc]; + + objc_disposeClassPair(c); +} + +- (void)registerDefaults:(NSDictionary *)dict +{ + defaults = [dict retain]; +} + +- (id)objectForKey:(NSString *)key +{ + NSObject *obj = (NSObject *) + CFPreferencesCopyAppValue ((CFStringRef) key, (CFStringRef) domain); + if (obj) + [obj autorelease]; + else if (defaults) + obj = [defaults objectForKey:key]; + return obj; +} + +- (void)setObject:(id)value forKey:(NSString *)key +{ + if (value && defaults) { + // If the value is the default, then remove it instead. + NSObject *def = [defaults objectForKey:key]; + if (def && [def isEqual:value]) + value = NULL; + } + CFPreferencesSetAppValue ((CFStringRef) key, + (CFPropertyListRef) value, + (CFStringRef) domain); +} + + +- (BOOL)synchronize +{ + return CFPreferencesAppSynchronize ((CFStringRef) domain); +} + + +// Make sure these all call our objectForKey. +// Might not be necessary, but safe. + +- (NSString *)stringForKey:(NSString *)key +{ + return [[self objectForKey:key] stringValue]; +} + +- (NSArray *)arrayForKey:(NSString *)key +{ + return (NSArray *) [self objectForKey:key]; +} + +- (NSDictionary *)dictionaryForKey:(NSString *)key +{ + return (NSDictionary *) [self objectForKey:key]; +} + +- (NSData *)dataForKey:(NSString *)key +{ + return (NSData *) [self objectForKey:key]; +} + +- (NSArray *)stringArrayForKey:(NSString *)key +{ + return (NSArray *) [self objectForKey:key]; +} + +- (NSInteger)integerForKey:(NSString *)key +{ + return [[self objectForKey:key] integerValue]; +} + +- (float)floatForKey:(NSString *)key +{ + return [[self objectForKey:key] floatValue]; +} + +- (double)doubleForKey:(NSString *)key +{ + return [[self objectForKey:key] doubleValue]; +} + +- (BOOL)boolForKey:(NSString *)key +{ + return [[self objectForKey:key] integerValue]; +} + +// Make sure these all call our setObject. +// Might not be necessary, but safe. + +- (void)removeObjectForKey:(NSString *)key +{ + [self setObject:NULL forKey:key]; +} + +- (void)setInteger:(NSInteger)value forKey:(NSString *)key +{ + [self setObject:[NSNumber numberWithInteger:value] forKey:key]; +} + +- (void)setFloat:(float)value forKey:(NSString *)key +{ + [self setObject:[NSNumber numberWithFloat:value] forKey:key]; +} + +- (void)setDouble:(double)value forKey:(NSString *)key +{ + [self setObject:[NSNumber numberWithDouble:value] forKey:key]; +} + +- (void)setBool:(BOOL)value forKey:(NSString *)key +{ + [self setObject:[NSNumber numberWithBool:value] forKey:key]; +} +@end + + +#endif // !USE_IPHONE + + @implementation PrefsReader /* Normally we read resources by looking up "KEY" in the database @@ -31,7 +213,7 @@ instead, so transform keys to "SAVERNAME.KEY". NOTE: This is duplicated in XScreenSaverConfigSheet.m, cause I suck. -*/ + */ - (NSString *) makeKey:(NSString *)key { # ifdef USE_IPHONE @@ -68,7 +250,7 @@ while (*val == ' ' || *val == '\t') val++; - int L = strlen(val); + unsigned long L = strlen(val); while (L > 0 && (val[L-1] == ' ' || val[L-1] == '\t')) val[--L] = 0; @@ -113,6 +295,7 @@ // Store the contents of 'defaults' into the real preferences database. NSDictionary *defsdict = [self defaultsToDict:defs]; [userDefaults registerDefaults:defsdict]; + [globalDefaults registerDefaults:UPDATER_DEFAULTS]; // Save a copy of the default options, since iOS doesn't have // [userDefaultsController initialValues]. @@ -129,8 +312,12 @@ userDefaultsController = [[NSUserDefaultsController alloc] initWithDefaults:userDefaults initialValues:defsdict]; + globalDefaultsController = + [[NSUserDefaultsController alloc] initWithDefaults:globalDefaults + initialValues:UPDATER_DEFAULTS]; # else // USE_IPHONE - userDefaultsController = userDefaults; + userDefaultsController = [userDefaults retain]; + globalDefaultsController = [userDefaults retain]; # endif // USE_IPHONE NSDictionary *optsdict = [NSMutableDictionary dictionaryWithCapacity:20]; @@ -146,6 +333,7 @@ // make sure there's no resource mentioned in options and not defaults. if (![defsdict objectForKey:nsresource]) { if (! (!strcmp(resource, "font") || // don't warn about these + !strcmp(resource, "foreground") || !strcmp(resource, "textLiteral") || !strcmp(resource, "textFile") || !strcmp(resource, "textURL") || @@ -186,12 +374,20 @@ #if 0 // Dump the entire resource database. + NSLog(@"userDefaults:"); NSDictionary *d = [userDefaults dictionaryRepresentation]; for (NSObject *key in [[d allKeys] sortedArrayUsingSelector:@selector(compare:)]) { NSObject *val = [d objectForKey:key]; NSLog (@"%@ = %@", key, val); } + NSLog(@"globalDefaults:"); + d = [globalDefaults dictionaryRepresentation]; + for (NSObject *key in [[d allKeys] + sortedArrayUsingSelector:@selector(compare:)]) { + NSObject *val = [d objectForKey:key]; + NSLog (@"%@ = %@", key, val); + } #endif } @@ -202,29 +398,50 @@ return userDefaultsController; } +- (NSUserDefaultsController *) globalDefaultsController +{ + NSAssert(globalDefaultsController, @"globalDefaultsController uninitialized"); + return globalDefaultsController; +} + - (NSDictionary *) defaultOptions { - NSAssert(defaultOptions, @"userDefaultsController uninitialized"); + NSAssert(defaultOptions, @"defaultOptions uninitialized"); return defaultOptions; } - (NSObject *) getObjectResource: (const char *) name { + // Only look in globalDefaults for updater preferences. + + static NSDictionary *updaterDefaults; + if (!updaterDefaults) { + updaterDefaults = UPDATER_DEFAULTS; + [updaterDefaults retain]; + } + + NSUserDefaults *defaults = + [updaterDefaults objectForKey:[NSString stringWithUTF8String:name]] ? + globalDefaults : + userDefaults; + + const char *name2 = name; while (1) { - NSString *key = [self makeCKey:name]; - NSObject *obj = [userDefaults objectForKey:key]; + NSString *key = [self makeCKey:name2]; + NSObject *obj = [defaults objectForKey:key]; if (obj) return obj; // If key is "foo.bar.baz", check "foo.bar.baz", "bar.baz", and "baz". // - const char *dot = strchr (name, '.'); + const char *dot = strchr (name2, '.'); if (dot && dot[1]) - name = dot + 1; + name2 = dot + 1; else - return nil; + break; } + return NULL; } @@ -361,8 +578,11 @@ # ifndef USE_IPHONE userDefaults = [ScreenSaverDefaults defaultsForModuleWithName:name]; + globalDefaults = [[GlobalDefaults alloc] initWithDomain:@UPDATER_DOMAIN + module:name]; # else // USE_IPHONE userDefaults = [NSUserDefaults standardUserDefaults]; + globalDefaults = [userDefaults retain]; # endif // USE_IPHONE // Convert "org.jwz.xscreensaver.NAME" to just "NAME". @@ -380,6 +600,8 @@ { [saver_name release]; [userDefaultsController release]; + [globalDefaultsController release]; + [globalDefaults release]; [super dealloc]; }