-/* xscreensaver, Copyright (c) 2006-2013 Jamie Zawinski <jwz@jwz.org>
+/* xscreensaver, Copyright (c) 2006-2015 Jamie Zawinski <jwz@jwz.org>
*
* Permission to use, copy, modify, distribute, and sell this software and its
* documentation for any purpose is hereby granted without fee, provided that
#endif
#import "PrefsReader.h"
+#import "Updater.h"
#import "screenhackI.h"
+#ifndef USE_IPHONE
+
+#include <objc/runtime.h>
+
+/* 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
instead, so transform keys to "SAVERNAME.KEY".
NOTE: This is duplicated in XScreenSaverConfigSheet.m, cause I suck.
-*/
+ */
- (NSString *) makeKey:(NSString *)key
{
# ifdef USE_IPHONE
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;
// 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].
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];
#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
}
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;
}
# 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".
{
[saver_name release];
[userDefaultsController release];
+ [globalDefaultsController release];
+ [globalDefaults release];
[super dealloc];
}