Merge pull request #5282 from electron/system-preferences

Add systemPreferences module
This commit is contained in:
Cheng Zhao 2016-04-26 08:21:43 +09:00
commit 63d0704490
19 changed files with 324 additions and 97 deletions

View file

@ -41,7 +41,6 @@
#if defined(OS_WIN)
#include "base/strings/utf_string_conversions.h"
#include "ui/base/win/shell.h"
#endif
using atom::Browser;
@ -332,12 +331,6 @@ void App::OnGpuProcessCrashed(base::TerminationStatus exit_code) {
Emit("gpu-process-crashed");
}
#if defined(OS_MACOSX)
void App::OnPlatformThemeChanged() {
Emit("platform-theme-changed");
}
#endif
base::FilePath App::GetPath(mate::Arguments* args, const std::string& name) {
bool succeed = false;
base::FilePath path;
@ -382,12 +375,6 @@ std::string App::GetLocale() {
return l10n_util::GetApplicationLocale("");
}
#if defined(OS_WIN)
bool App::IsAeroGlassEnabled() {
return ui::win::IsAeroGlassEnabled();
}
#endif
bool App::MakeSingleInstance(
const ProcessSingleton::NotificationCallback& callback) {
if (process_singleton_.get())
@ -471,13 +458,10 @@ void App::BuildPrototype(
#if defined(OS_MACOSX)
.SetMethod("hide", base::Bind(&Browser::Hide, browser))
.SetMethod("show", base::Bind(&Browser::Show, browser))
.SetMethod("isDarkMode",
base::Bind(&Browser::IsDarkMode, browser))
#endif
#if defined(OS_WIN)
.SetMethod("setUserTasks",
base::Bind(&Browser::SetUserTasks, browser))
.SetMethod("isAeroGlassEnabled", &App::IsAeroGlassEnabled)
#endif
.SetMethod("setPath", &App::SetPath)
.SetMethod("getPath", &App::GetPath)

View file

@ -92,10 +92,6 @@ class App : public AtomBrowserClient::Delegate,
// content::GpuDataManagerObserver:
void OnGpuProcessCrashed(base::TerminationStatus exit_code) override;
#if defined(OS_MACOSX)
void OnPlatformThemeChanged() override;
#endif
private:
// Get/Set the pre-defined path in PathService.
base::FilePath GetPath(mate::Arguments* args, const std::string& name);
@ -114,10 +110,6 @@ class App : public AtomBrowserClient::Delegate,
const net::CompletionCallback& callback);
#endif
#if defined(OS_WIN)
bool IsAeroGlassEnabled();
#endif
scoped_ptr<ProcessSingleton> process_singleton_;
#if defined(USE_NSS_CERTS)

View file

@ -53,6 +53,7 @@ v8::Local<v8::Value> PowerMonitor::Create(v8::Isolate* isolate) {
// static
void PowerMonitor::BuildPrototype(
v8::Isolate* isolate, v8::Local<v8::ObjectTemplate> prototype) {
mate::ObjectTemplateBuilder(isolate, prototype);
}
} // namespace api

View file

@ -0,0 +1,75 @@
// Copyright (c) 2016 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "atom/browser/api/atom_api_system_preferences.h"
#include "atom/common/native_mate_converters/callback.h"
#include "atom/common/node_includes.h"
#include "native_mate/dictionary.h"
#if defined(OS_WIN)
#include "ui/base/win/shell.h"
#endif
namespace atom {
namespace api {
SystemPreferences::SystemPreferences(v8::Isolate* isolate) {
Init(isolate);
}
SystemPreferences::~SystemPreferences() {
}
#if defined(OS_WIN)
bool SystemPreferences::IsAeroGlassEnabled() {
return ui::win::IsAeroGlassEnabled();
}
#endif
#if !defined(OS_MACOSX)
bool SystemPreferences::IsDarkMode() {
return false;
}
#endif
// static
mate::Handle<SystemPreferences> SystemPreferences::Create(
v8::Isolate* isolate) {
return mate::CreateHandle(isolate, new SystemPreferences(isolate));
}
// static
void SystemPreferences::BuildPrototype(
v8::Isolate* isolate, v8::Local<v8::ObjectTemplate> prototype) {
mate::ObjectTemplateBuilder(isolate, prototype)
#if defined(OS_WIN)
.SetMethod("isAeroGlassEnabled", &SystemPreferences::IsAeroGlassEnabled)
#elif defined(OS_MACOSX)
.SetMethod("subscribeNotification",
&SystemPreferences::SubscribeNotification)
.SetMethod("unsubscribeNotification",
&SystemPreferences::UnsubscribeNotification)
.SetMethod("getUserDefault", &SystemPreferences::GetUserDefault)
#endif
.SetMethod("isDarkMode", &SystemPreferences::IsDarkMode);
}
} // namespace api
} // namespace atom
namespace {
void Initialize(v8::Local<v8::Object> exports, v8::Local<v8::Value> unused,
v8::Local<v8::Context> context, void* priv) {
v8::Isolate* isolate = context->GetIsolate();
mate::Dictionary dict(isolate, exports);
dict.Set("systemPreferences", atom::api::SystemPreferences::Create(isolate));
}
} // namespace
NODE_MODULE_CONTEXT_AWARE_BUILTIN(atom_browser_system_preferences, Initialize);

View file

@ -0,0 +1,48 @@
// Copyright (c) 2016 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef ATOM_BROWSER_API_ATOM_API_SYSTEM_PREFERENCES_H_
#define ATOM_BROWSER_API_ATOM_API_SYSTEM_PREFERENCES_H_
#include <string>
#include "atom/browser/api/event_emitter.h"
#include "base/callback.h"
#include "native_mate/handle.h"
namespace atom {
namespace api {
class SystemPreferences : public mate::EventEmitter<SystemPreferences> {
public:
static mate::Handle<SystemPreferences> Create(v8::Isolate* isolate);
static void BuildPrototype(v8::Isolate* isolate,
v8::Local<v8::ObjectTemplate> prototype);
#if defined(OS_WIN)
bool IsAeroGlassEnabled();
#elif defined(OS_MACOSX)
int SubscribeNotification(const std::string& name,
const base::Closure& callback);
void UnsubscribeNotification(int id);
v8::Local<v8::Value> GetUserDefault(const std::string& name,
const std::string& type);
#endif
bool IsDarkMode();
protected:
explicit SystemPreferences(v8::Isolate* isolate);
~SystemPreferences() override;
private:
DISALLOW_COPY_AND_ASSIGN(SystemPreferences);
};
} // namespace api
} // namespace atom
#endif // ATOM_BROWSER_API_ATOM_API_SYSTEM_PREFERENCES_H_

View file

@ -0,0 +1,83 @@
// Copyright (c) 2016 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "atom/browser/api/atom_api_system_preferences.h"
#include <map>
#import <Cocoa/Cocoa.h>
#include "atom/common/native_mate_converters/gurl_converter.h"
#include "base/strings/sys_string_conversions.h"
#include "net/base/mac/url_conversions.h"
namespace atom {
namespace api {
namespace {
int g_next_id = 0;
// The map to convert |id| to |int|.
std::map<int, id> g_id_map;
} // namespace
int SystemPreferences::SubscribeNotification(const std::string& name,
const base::Closure& callback) {
int request_id = g_next_id++;
__block base::Closure copied_callback = callback;
g_id_map[request_id] = [[NSDistributedNotificationCenter defaultCenter]
addObserverForName:base::SysUTF8ToNSString(name)
object:nil
queue:nil
usingBlock:^(NSNotification* notification) {
copied_callback.Run();
}
];
return request_id;
}
void SystemPreferences::UnsubscribeNotification(int request_id) {
auto iter = g_id_map.find(request_id);
if (iter != g_id_map.end()) {
id observer = iter->second;
[[NSDistributedNotificationCenter defaultCenter] removeObserver:observer];
g_id_map.erase(iter);
}
}
v8::Local<v8::Value> SystemPreferences::GetUserDefault(
const std::string& name, const std::string& type) {
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
NSString* key = base::SysUTF8ToNSString(name);
if (type == "string") {
return mate::StringToV8(
isolate(), base::SysNSStringToUTF8([defaults stringForKey:key]));
} else if (type == "boolean") {
return v8::Boolean::New(isolate(), [defaults boolForKey:key]);
} else if (type == "float") {
return v8::Number::New(isolate(), [defaults floatForKey:key]);
} else if (type == "integer") {
return v8::Integer::New(isolate(), [defaults integerForKey:key]);
} else if (type == "double") {
return v8::Number::New(isolate(), [defaults doubleForKey:key]);
} else if (type == "url") {
return mate::ConvertToV8(
isolate(), net::GURLWithNSURL([defaults URLForKey:key]));
} else {
return v8::Undefined(isolate());
}
}
bool SystemPreferences::IsDarkMode() {
NSString* mode = [[NSUserDefaults standardUserDefaults]
stringForKey:@"AppleInterfaceStyle"];
return [mode isEqualToString:@"Dark"];
}
} // namespace api
} // namespace atom

View file

@ -187,8 +187,4 @@ void Browser::OnWindowAllClosed() {
FOR_EACH_OBSERVER(BrowserObserver, observers_, OnWindowAllClosed());
}
void Browser::PlatformThemeChanged() {
FOR_EACH_OBSERVER(BrowserObserver, observers_, OnPlatformThemeChanged());
}
} // namespace atom

View file

@ -89,9 +89,6 @@ class Browser : public WindowListObserver {
// Show the application.
void Show();
// Check if the system is in Dark Mode.
bool IsDarkMode();
// Bounce the dock icon.
enum BounceType {
BOUNCE_CRITICAL = 0,
@ -151,9 +148,6 @@ class Browser : public WindowListObserver {
// Request basic auth login.
void RequestLogin(LoginHandler* login_handler);
// Tell the application that plaform's theme changed.
void PlatformThemeChanged();
void AddObserver(BrowserObserver* obs) {
observers_.AddObserver(obs);
}

View file

@ -27,11 +27,6 @@ void Browser::Show() {
[[AtomApplication sharedApplication] unhide:nil];
}
bool Browser::IsDarkMode() {
NSString *mode = [[NSUserDefaults standardUserDefaults] stringForKey:@"AppleInterfaceStyle"];
return [mode isEqualToString: @"Dark"];
}
void Browser::AddRecentDocument(const base::FilePath& path) {
NSString* path_string = base::mac::FilePathToNSString(path);
if (!path_string)

View file

@ -45,8 +45,6 @@ class BrowserObserver {
// The browser requests HTTP login.
virtual void OnLogin(LoginHandler* login_handler) {}
virtual void OnPlatformThemeChanged() {}
protected:
virtual ~BrowserObserver() {}
};

View file

@ -24,9 +24,6 @@
// Don't add the "Enter Full Screen" menu item automatically.
[[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"NSFullScreenMenuItemEverywhere"];
// Add observer to monitor the system's Dark Mode theme.
[[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(platformThemeChanged:) name:@"AppleInterfaceThemeChangedNotification" object:nil];
atom::Browser::Get()->WillFinishLaunching();
}
@ -62,8 +59,4 @@
return flag;
}
- (void)platformThemeChanged:(NSNotification *)notify {
atom::Browser::Get()->PlatformThemeChanged();
}
@end

View file

@ -44,6 +44,7 @@ REFERENCE_MODULE(atom_browser_power_save_blocker);
REFERENCE_MODULE(atom_browser_protocol);
REFERENCE_MODULE(atom_browser_global_shortcut);
REFERENCE_MODULE(atom_browser_session);
REFERENCE_MODULE(atom_browser_system_preferences);
REFERENCE_MODULE(atom_browser_tray);
REFERENCE_MODULE(atom_browser_web_contents);
REFERENCE_MODULE(atom_browser_web_view_manager);

View file

@ -62,6 +62,7 @@ an issue:
* [powerSaveBlocker](api/power-save-blocker.md)
* [protocol](api/protocol.md)
* [session](api/session.md)
* [systemPreferences](api/system-preferences.md)
* [webContents](api/web-contents.md)
* [Tray](api/tray.md)

View file

@ -228,10 +228,6 @@ app.on('login', function(event, webContents, request, authInfo, callback) {
Emitted when the gpu process crashes.
### Event: 'platform-theme-changed' _OS X_
Emitted when the system's Dark Mode theme is toggled.
## Methods
The `app` object has the following methods:
@ -480,40 +476,6 @@ app.on('ready', function() {
Changes the [Application User Model ID][app-user-model-id] to `id`.
### `app.isAeroGlassEnabled()` _Windows_
This method returns `true` if [DWM composition](https://msdn.microsoft.com/en-us/library/windows/desktop/aa969540.aspx)
(Aero Glass) is enabled, and `false` otherwise. You can use it to determine if
you should create a transparent window or not (transparent windows won't work
correctly when DWM composition is disabled).
Usage example:
```javascript
let browserOptions = {width: 1000, height: 800};
// Make the window transparent only if the platform supports it.
if (process.platform !== 'win32' || app.isAeroGlassEnabled()) {
browserOptions.transparent = true;
browserOptions.frame = false;
}
// Create the window.
win = new BrowserWindow(browserOptions);
// Navigate.
if (browserOptions.transparent) {
win.loadURL('file://' + __dirname + '/index.html');
} else {
// No transparency, so we load a fallback that uses basic styles.
win.loadURL('file://' + __dirname + '/fallback.html');
}
```
### `app.isDarkMode()` _OS X_
This method returns `true` if the system is in Dark Mode, and `false` otherwise.
### `app.importCertificate(options, callback)` _LINUX_
* `options` Object

View file

@ -0,0 +1,79 @@
# systemPreferences
> Get system preferences.
## Methods
### `systemPreferences.isDarkMode()` _OS X_
This method returns `true` if the system is in Dark Mode, and `false` otherwise.
### `systemPreferences.subscribeNotification(event, callback)` _OS X_
* `event` String
* `callback` Function
Subscribes to native notifications of OS X, `callback` will be called when the
corresponding `event` happens. The `id` of the subscriber is returned, which can
be used to unsubscribe the `event`.
Under the hood this API subscribes to `NSDistributedNotificationCenter`,
possible values of `event` are:
* `AppleInterfaceThemeChangedNotification`
* `AppleAquaColorVariantChanged`
* `AppleColorPreferencesChangedNotification`
* `AppleShowScrollBarsSettingChanged`
### `systemPreferences.unsubscribeNotification(id)` _OS X_
* `id` Integer
Removes the subscriber with `id`.
### `systemPreferences.getUserDefault(key, type)` _OS X_
* `key` String
* `type` String - Can be `string`, `boolean`, `integer`, `float`, `double`,
`url`.
Get the value of `key` in system preferences.
This API reads from `NSUserDefaults` on OS X, some popular `key` and `type`s
are:
* `AppleInterfaceStyle: string`
* `AppleAquaColorVariant: integer`
* `AppleHighlightColor: string`
* `AppleShowScrollBars: string`
### `systemPreferences.isAeroGlassEnabled()` _Windows_
This method returns `true` if [DWM composition][dwm-composition] (Aero Glass) is
enabled, and `false` otherwise.
An example of using it to determine if you should create a transparent window or
not (transparent windows won't work correctly when DWM composition is disabled):
```javascript
let browserOptions = {width: 1000, height: 800};
// Make the window transparent only if the platform supports it.
if (process.platform !== 'win32' || app.isAeroGlassEnabled()) {
browserOptions.transparent = true;
browserOptions.frame = false;
}
// Create the window.
let win = new BrowserWindow(browserOptions);
// Navigate.
if (browserOptions.transparent) {
win.loadURL('file://' + __dirname + '/index.html');
} else {
// No transparency, so we load a fallback that uses basic styles.
win.loadURL('file://' + __dirname + '/fallback.html');
}
```
[dwm-composition]:https://msdn.microsoft.com/en-us/library/windows/desktop/aa969540.aspx

View file

@ -28,6 +28,7 @@
'lib/browser/api/protocol.js',
'lib/browser/api/session.js',
'lib/browser/api/screen.js',
'lib/browser/api/system-preferences.js',
'lib/browser/api/tray.js',
'lib/browser/api/web-contents.js',
'lib/browser/chrome-extension.js',
@ -115,6 +116,9 @@
'atom/browser/api/atom_api_screen.h',
'atom/browser/api/atom_api_session.cc',
'atom/browser/api/atom_api_session.h',
'atom/browser/api/atom_api_system_preferences.cc',
'atom/browser/api/atom_api_system_preferences.h',
'atom/browser/api/atom_api_system_preferences_mac.mm',
'atom/browser/api/atom_api_tray.cc',
'atom/browser/api/atom_api_tray.h',
'atom/browser/api/atom_api_web_contents.cc',

View file

@ -1,8 +1,7 @@
'use strict'
const deprecate = require('electron').deprecate
const session = require('electron').session
const Menu = require('electron').Menu
const electron = require('electron')
const {deprecate, session, Menu} = electron
const EventEmitter = require('events').EventEmitter
const bindings = process.atomBinding('app')
@ -65,39 +64,49 @@ for (i = 0, len = ref1.length; i < len; i++) {
}
// Deprecated.
app.getHomeDir = deprecate('app.getHomeDir', 'app.getPath', function () {
return this.getPath('home')
})
app.getDataPath = deprecate('app.getDataPath', 'app.getPath', function () {
return this.getPath('userData')
})
app.setDataPath = deprecate('app.setDataPath', 'app.setPath', function (path) {
return this.setPath('userData', path)
})
app.resolveProxy = deprecate('app.resolveProxy', 'session.defaultSession.resolveProxy', function (url, callback) {
return session.defaultSession.resolveProxy(url, callback)
})
deprecate.rename(app, 'terminate', 'quit')
deprecate.event(app, 'finish-launching', 'ready', function () {
// give default app a chance to setup default menu.
setImmediate(() => {
this.emit('finish-launching')
})
})
deprecate.event(app, 'activate-with-no-open-windows', 'activate', function (event, hasVisibleWindows) {
if (!hasVisibleWindows) {
return this.emit('activate-with-no-open-windows', event)
}
})
deprecate.event(app, 'select-certificate', 'select-client-certificate')
if (process.platform === 'win32') {
app.isAeroGlassEnabled = deprecate('app.isAeroGlassEnabled', 'systemPreferences.isAeroGlassEnabled', function () {
return electron.systemPreferences.isAeroGlassEnabled()
})
} else if (process.platform === 'darwin') {
app.isDarkMode = deprecate('app.isDarkMode', 'systemPreferences.isDarkMode', function () {
return electron.systemPreferences.isDarkMode()
})
app.on = app.addListener = function (event, listener) {
if (event === 'platform-theme-changed') {
deprecate.warn('platform-theme-changed event', "systemPreferences.subscribeNotification('AppleInterfaceThemeChangedNotification', callback)")
electron.systemPreferences.subscribeNotification('AppleInterfaceThemeChangedNotification', function () {
app.emit('platform-theme-changed')
})
}
EventEmitter.prototype.addListener.call(app, event, listener)
}
}
// Wrappers for native classes.
var wrapDownloadItem = function (downloadItem) {

View file

@ -89,6 +89,12 @@ Object.defineProperties(exports, {
return require('../session')
}
},
systemPreferences: {
enumerable: true,
get: function () {
return require('../system-preferences')
}
},
Tray: {
enumerable: true,
get: function () {

View file

@ -0,0 +1,6 @@
const {EventEmitter} = require('events')
const {systemPreferences} = process.atomBinding('system_preferences')
Object.setPrototypeOf(systemPreferences, EventEmitter.prototype)
module.exports = systemPreferences