diff --git a/atom/browser/api/atom_api_system_preferences.cc b/atom/browser/api/atom_api_system_preferences.cc index 7bdc4feee4e2..ad35d548d9e4 100644 --- a/atom/browser/api/atom_api_system_preferences.cc +++ b/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 diff --git a/atom/browser/api/atom_api_system_preferences.h b/atom/browser/api/atom_api_system_preferences.h index ee55601b5364..aa7f946eb81d 100644 --- a/atom/browser/api/atom_api_system_preferences.h +++ b/atom/browser/api/atom_api_system_preferences.h @@ -67,6 +67,9 @@ class SystemPreferences : public mate::EventEmitter void UnsubscribeLocalNotification(int request_id); v8::Local 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(); diff --git a/atom/browser/api/atom_api_system_preferences_mac.mm b/atom/browser/api/atom_api_system_preferences_mac.mm index edb875c0b243..877ad1a13288 100644 --- a/atom/browser/api/atom_api_system_preferences_mac.mm +++ b/atom/browser/api/atom_api_system_preferences_mac.mm @@ -144,6 +144,91 @@ v8::Local 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"]; diff --git a/atom/browser/mac/dict_util.h b/atom/browser/mac/dict_util.h index 74a2b7234c04..a5b973698f84 100644 --- a/atom/browser/mac/dict_util.h +++ b/atom/browser/mac/dict_util.h @@ -16,13 +16,15 @@ class DictionaryValue; namespace atom { +NSArray* ListValueToNSArray(const base::ListValue& value); + +std::unique_ptr NSArrayToListValue(NSArray* arr); + NSDictionary* DictionaryValueToNSDictionary(const base::DictionaryValue& value); std::unique_ptr NSDictionaryToDictionaryValue( NSDictionary* dict); -std::unique_ptr NSArrayToListValue(NSArray* arr); - } // namespace atom #endif // ATOM_BROWSER_MAC_DICT_UTIL_H_ diff --git a/atom/browser/mac/dict_util.mm b/atom/browser/mac/dict_util.mm index 8692f001f6a8..136ec1aba547 100644 --- a/atom/browser/mac/dict_util.mm +++ b/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 NSArrayToListValue(NSArray* arr) { if (!arr) return nullptr; diff --git a/docs/api/system-preferences.md b/docs/api/system-preferences.md index 535ae403ef06..f7569e6d11f7 100644 --- a/docs/api/system-preferences.md +++ b/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: +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` +* `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_ diff --git a/spec/api-system-preferences-spec.js b/spec/api-system-preferences-spec.js index e893a7c55e24..1c03d70d9860 100644 --- a/spec/api-system-preferences-spec.js +++ b/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')