Browse Source

Add systemPreferences.setUserDefault for macOS

This API can be used to e.g. enable key repeat by setting
`ApplePressAndHoldEnabled` to `false` (see also #47).
Birunthan Mohanathas 8 years ago
parent
commit
486b6b9096

+ 1 - 0
atom/browser/api/atom_api_system_preferences.cc

@@ -67,6 +67,7 @@ void SystemPreferences::BuildPrototype(
       .SetMethod("unsubscribeLocalNotification",
                  &SystemPreferences::UnsubscribeLocalNotification)
       .SetMethod("getUserDefault", &SystemPreferences::GetUserDefault)
+      .SetMethod("setUserDefault", &SystemPreferences::SetUserDefault)
       .SetMethod("isSwipeTrackingFromScrollEventsEnabled",
                  &SystemPreferences::IsSwipeTrackingFromScrollEventsEnabled)
 #endif

+ 3 - 0
atom/browser/api/atom_api_system_preferences.h

@@ -67,6 +67,9 @@ class SystemPreferences : public mate::EventEmitter<SystemPreferences>
   void UnsubscribeLocalNotification(int request_id);
   v8::Local<v8::Value> GetUserDefault(const std::string& name,
                                       const std::string& type);
+  void SetUserDefault(const std::string& name,
+                      const std::string& type,
+                      mate::Arguments* args);
   bool IsSwipeTrackingFromScrollEventsEnabled();
 #endif
   bool IsDarkMode();

+ 85 - 0
atom/browser/api/atom_api_system_preferences_mac.mm

@@ -144,6 +144,91 @@ v8::Local<v8::Value> SystemPreferences::GetUserDefault(
   }
 }
 
+void SystemPreferences::SetUserDefault(const std::string& name,
+                                       const std::string& type,
+                                       mate::Arguments* args) {
+  const auto throwConversionError = [&] {
+    args->ThrowError("Unable to convert value to: " + type);
+  };
+
+  NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
+  NSString* key = base::SysUTF8ToNSString(name);
+  if (type == "string") {
+    std::string value;
+    if (!args->GetNext(&value)) {
+      throwConversionError();
+      return;
+    }
+
+    [defaults setObject:base::SysUTF8ToNSString(value) forKey:key];
+  } else if (type == "boolean") {
+    bool value;
+    if (!args->GetNext(&value)) {
+      throwConversionError();
+      return;
+    }
+
+    [defaults setBool:value forKey:key];
+  } else if (type == "float") {
+    float value;
+    if (!args->GetNext(&value)) {
+      throwConversionError();
+      return;
+    }
+
+    [defaults setFloat:value forKey:key];
+  } else if (type == "integer") {
+    int value;
+    if (!args->GetNext(&value)) {
+      throwConversionError();
+      return;
+    }
+
+    [defaults setInteger:value forKey:key];
+  } else if (type == "double") {
+    double value;
+    if (!args->GetNext(&value)) {
+      throwConversionError();
+      return;
+    }
+
+    [defaults setDouble:value forKey:key];
+  } else if (type == "url") {
+    GURL value;
+    if (!args->GetNext(&value)) {
+      throwConversionError();
+      return;
+    }
+
+    if (NSURL* url = net::NSURLWithGURL(value)) {
+      [defaults setURL:url forKey:key];
+    }
+  } else if (type == "array") {
+    base::ListValue value;
+    if (!args->GetNext(&value)) {
+      throwConversionError();
+      return;
+    }
+
+    if (NSArray* array = ListValueToNSArray(value)) {
+      [defaults setObject:array forKey:key];
+    }
+  } else if (type == "dictionary") {
+    base::DictionaryValue value;
+    if (!args->GetNext(&value)) {
+      throwConversionError();
+      return;
+    }
+
+    if (NSDictionary* dict = DictionaryValueToNSDictionary(value)) {
+      [defaults setObject:dict forKey:key];
+    }
+  } else {
+    args->ThrowError("Invalid type: " + type);
+    return;
+  }
+}
+
 bool SystemPreferences::IsDarkMode() {
   NSString* mode = [[NSUserDefaults standardUserDefaults]
       stringForKey:@"AppleInterfaceStyle"];

+ 4 - 2
atom/browser/mac/dict_util.h

@@ -16,13 +16,15 @@ class DictionaryValue;
 
 namespace atom {
 
+NSArray* ListValueToNSArray(const base::ListValue& value);
+
+std::unique_ptr<base::ListValue> NSArrayToListValue(NSArray* arr);
+
 NSDictionary* DictionaryValueToNSDictionary(const base::DictionaryValue& value);
 
 std::unique_ptr<base::DictionaryValue> NSDictionaryToDictionaryValue(
     NSDictionary* dict);
 
-std::unique_ptr<base::ListValue> NSArrayToListValue(NSArray* arr);
-
 }  // namespace atom
 
 #endif  // ATOM_BROWSER_MAC_DICT_UTIL_H_

+ 12 - 0
atom/browser/mac/dict_util.mm

@@ -10,6 +10,18 @@
 
 namespace atom {
 
+NSArray* ListValueToNSArray(const base::ListValue& value) {
+  std::string json;
+  if (!base::JSONWriter::Write(value, &json))
+    return nil;
+  NSData* jsonData = [NSData dataWithBytes:json.c_str() length:json.length()];
+  id obj =
+      [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:nil];
+  if (![obj isKindOfClass:[NSArray class]])
+    return nil;
+  return obj;
+}
+
 std::unique_ptr<base::ListValue> NSArrayToListValue(NSArray* arr) {
   if (!arr)
     return nullptr;

+ 24 - 10
docs/api/system-preferences.md

@@ -114,16 +114,30 @@ Same as `unsubscribeNotification`, but removes the subscriber from `NSNotificati
 
 Get the value of `key` in system preferences.
 
-This API reads from `NSUserDefaults` on macOS, some popular `key` and `type`s
-are:
-
-* `AppleInterfaceStyle: string`
-* `AppleAquaColorVariant: integer`
-* `AppleHighlightColor: string`
-* `AppleShowScrollBars: string`
-* `NSNavRecentPlaces: array`
-* `NSPreferredWebServices: dictionary`
-* `NSUserDictionaryReplacementItems: array`
+This API uses `NSUserDefaults` on macOS. Some popular `key` and `type`s are:
+
+* `AppleInterfaceStyle`:  `string`
+* `AppleAquaColorVariant`:  `integer`
+* `AppleHighlightColor`:  `string`
+* `AppleShowScrollBars`:  `string`
+* `NSNavRecentPlaces`:  `array`
+* `NSPreferredWebServices`:  `dictionary`
+* `NSUserDictionaryReplacementItems`:  `array`
+
+### `systemPreferences.setUserDefault(key, type, value)` _macOS_
+
+* `key` String
+* `type` String - See [`getUserDefault`][#systempreferencesgetuserdefaultkey-type-macos]
+* `value` String
+
+Set the value of `key` in system preferences.
+
+Note that `type` should match actual type of `value`. An exception is thrown
+if they don't.
+
+This API uses `NSUserDefaults` on macOS. Some popular `key` and `type`s are:
+
+* `ApplePressAndHoldEnabled`:  `boolean`
 
 ### `systemPreferences.isAeroGlassEnabled()` _Windows_
 

+ 41 - 0
spec/api-system-preferences-spec.js

@@ -59,6 +59,47 @@ describe('systemPreferences module', function () {
     })
   })
 
+  describe('systemPreferences.setUserDefault(key, type, value)', () => {
+    if (process.platform !== 'darwin') {
+      return
+    }
+
+    const KEY = 'SystemPreferencesTest'
+
+    const TEST_CASES = [
+      ['string', 'abc'],
+      ['boolean', true],
+      ['float', 2.5],
+      ['double', 10.1],
+      ['integer', 11],
+      ['url', 'https://github.com/electron'],
+      ['array', [1, 2, 3]],
+      ['dictionary', {'a': 1, 'b': 2}]
+    ]
+
+    it('sets values', () => {
+      for (const [type, value] of TEST_CASES) {
+        systemPreferences.setUserDefault(KEY, type, value)
+        const retrievedValue = systemPreferences.getUserDefault(KEY, type)
+        assert.deepEqual(retrievedValue, value)
+      }
+    })
+
+    it('throws when type and value conflict', () => {
+      for (const [type, value] of TEST_CASES) {
+        assert.throws(() => {
+          systemPreferences.setUserDefault(KEY, type, typeof value === 'string' ? {} : 'foo')
+        }, `Unable to convert value to: ${type}`)
+      }
+    })
+
+    it('throws when type is not valid', () => {
+      assert.throws(() => {
+        systemPreferences.setUserDefault(KEY, 'abc', 'foo')
+      }, 'Invalid type: abc')
+    })
+  })
+
   describe('systemPreferences.isInvertedColorScheme()', function () {
     it('returns a boolean', function () {
       assert.equal(typeof systemPreferences.isInvertedColorScheme(), 'boolean')