From http://www.jwz.org/xscreensaver/xscreensaver-5.30.tar.gz
[xscreensaver] / OSX / PrefsReader.m
index 4f42fa90e9f3b8d289d73aec242d692597d61c40..4044280294aeb2fb07158b1ff2829aab25fce3f5 100644 (file)
 #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 && 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];
 }