diff --git a/atom/browser/api/atom_api_app.cc b/atom/browser/api/atom_api_app.cc index b35589ecad33..ce20ce96deda 100644 --- a/atom/browser/api/atom_api_app.cc +++ b/atom/browser/api/atom_api_app.cc @@ -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) diff --git a/atom/browser/api/atom_api_app.h b/atom/browser/api/atom_api_app.h index 354ea38d884b..c99d5df77937 100644 --- a/atom/browser/api/atom_api_app.h +++ b/atom/browser/api/atom_api_app.h @@ -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 process_singleton_; #if defined(USE_NSS_CERTS) diff --git a/atom/browser/api/atom_api_power_monitor.cc b/atom/browser/api/atom_api_power_monitor.cc index 15220c117c5b..32c50c696c7f 100644 --- a/atom/browser/api/atom_api_power_monitor.cc +++ b/atom/browser/api/atom_api_power_monitor.cc @@ -53,6 +53,7 @@ v8::Local PowerMonitor::Create(v8::Isolate* isolate) { // static void PowerMonitor::BuildPrototype( v8::Isolate* isolate, v8::Local prototype) { + mate::ObjectTemplateBuilder(isolate, prototype); } } // namespace api diff --git a/atom/browser/api/atom_api_system_preferences.cc b/atom/browser/api/atom_api_system_preferences.cc new file mode 100644 index 000000000000..b8c665456ade --- /dev/null +++ b/atom/browser/api/atom_api_system_preferences.cc @@ -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::Create( + v8::Isolate* isolate) { + return mate::CreateHandle(isolate, new SystemPreferences(isolate)); +} + +// static +void SystemPreferences::BuildPrototype( + v8::Isolate* isolate, v8::Local 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 exports, v8::Local unused, + v8::Local 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); diff --git a/atom/browser/api/atom_api_system_preferences.h b/atom/browser/api/atom_api_system_preferences.h new file mode 100644 index 000000000000..fed1c52247b7 --- /dev/null +++ b/atom/browser/api/atom_api_system_preferences.h @@ -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 + +#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 { + public: + static mate::Handle Create(v8::Isolate* isolate); + + static void BuildPrototype(v8::Isolate* isolate, + v8::Local 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 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_ diff --git a/atom/browser/api/atom_api_system_preferences_mac.mm b/atom/browser/api/atom_api_system_preferences_mac.mm new file mode 100644 index 000000000000..2d12b278ae92 --- /dev/null +++ b/atom/browser/api/atom_api_system_preferences_mac.mm @@ -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 + +#import + +#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 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 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 diff --git a/atom/browser/browser.cc b/atom/browser/browser.cc index b60df0dd44a5..b3c7a59e08b5 100644 --- a/atom/browser/browser.cc +++ b/atom/browser/browser.cc @@ -187,8 +187,4 @@ void Browser::OnWindowAllClosed() { FOR_EACH_OBSERVER(BrowserObserver, observers_, OnWindowAllClosed()); } -void Browser::PlatformThemeChanged() { - FOR_EACH_OBSERVER(BrowserObserver, observers_, OnPlatformThemeChanged()); -} - } // namespace atom diff --git a/atom/browser/browser.h b/atom/browser/browser.h index 67aae152c317..0f1dbe993118 100644 --- a/atom/browser/browser.h +++ b/atom/browser/browser.h @@ -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); } diff --git a/atom/browser/browser_mac.mm b/atom/browser/browser_mac.mm index 0294894fcd61..11fd3aa6d357 100644 --- a/atom/browser/browser_mac.mm +++ b/atom/browser/browser_mac.mm @@ -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) diff --git a/atom/browser/browser_observer.h b/atom/browser/browser_observer.h index da327eb90a02..f6d76bc13fb3 100644 --- a/atom/browser/browser_observer.h +++ b/atom/browser/browser_observer.h @@ -45,8 +45,6 @@ class BrowserObserver { // The browser requests HTTP login. virtual void OnLogin(LoginHandler* login_handler) {} - virtual void OnPlatformThemeChanged() {} - protected: virtual ~BrowserObserver() {} }; diff --git a/atom/browser/mac/atom_application_delegate.mm b/atom/browser/mac/atom_application_delegate.mm index f4db929bf575..7662162ab618 100644 --- a/atom/browser/mac/atom_application_delegate.mm +++ b/atom/browser/mac/atom_application_delegate.mm @@ -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 diff --git a/atom/common/node_bindings.cc b/atom/common/node_bindings.cc index 86fb3a6d03a3..31105886eb7c 100644 --- a/atom/common/node_bindings.cc +++ b/atom/common/node_bindings.cc @@ -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); diff --git a/docs/README.md b/docs/README.md index 428d2f8bd7b1..c8bd425d9759 100644 --- a/docs/README.md +++ b/docs/README.md @@ -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) diff --git a/docs/api/app.md b/docs/api/app.md index 8a2cdcfd9224..0aca703ba51d 100644 --- a/docs/api/app.md +++ b/docs/api/app.md @@ -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 diff --git a/docs/api/system-preferences.md b/docs/api/system-preferences.md new file mode 100644 index 000000000000..8fdd2b54cb0b --- /dev/null +++ b/docs/api/system-preferences.md @@ -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 diff --git a/filenames.gypi b/filenames.gypi index a1c704c348b1..c3eca34528df 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -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', diff --git a/lib/browser/api/app.js b/lib/browser/api/app.js index 48a27cfc648d..0487270ce7b3 100644 --- a/lib/browser/api/app.js +++ b/lib/browser/api/app.js @@ -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) { diff --git a/lib/browser/api/exports/electron.js b/lib/browser/api/exports/electron.js index bd8285401c79..9d873663029c 100644 --- a/lib/browser/api/exports/electron.js +++ b/lib/browser/api/exports/electron.js @@ -89,6 +89,12 @@ Object.defineProperties(exports, { return require('../session') } }, + systemPreferences: { + enumerable: true, + get: function () { + return require('../system-preferences') + } + }, Tray: { enumerable: true, get: function () { diff --git a/lib/browser/api/system-preferences.js b/lib/browser/api/system-preferences.js new file mode 100644 index 000000000000..6ba1750507c0 --- /dev/null +++ b/lib/browser/api/system-preferences.js @@ -0,0 +1,6 @@ +const {EventEmitter} = require('events') +const {systemPreferences} = process.atomBinding('system_preferences') + +Object.setPrototypeOf(systemPreferences, EventEmitter.prototype) + +module.exports = systemPreferences