feat: add APIs to support mojave dark modes (#14755)

* feat: add APIs to support mojave dark mode

Closes #13387

* docs: fix system-prefs typo
This commit is contained in:
Samuel Attard 2018-09-28 01:33:31 +10:00 committed by Charles Kerr
parent 8963529238
commit 0d2a0c7583
8 changed files with 204 additions and 1 deletions

View file

@ -77,6 +77,12 @@ void SystemPreferences::BuildPrototype(
.SetMethod("removeUserDefault", &SystemPreferences::RemoveUserDefault)
.SetMethod("isSwipeTrackingFromScrollEventsEnabled",
&SystemPreferences::IsSwipeTrackingFromScrollEventsEnabled)
.SetMethod("getEffectiveAppearance",
&SystemPreferences::GetEffectiveAppearance)
.SetMethod("getAppLevelAppearance",
&SystemPreferences::GetAppLevelAppearance)
.SetMethod("setAppLevelAppearance",
&SystemPreferences::SetAppLevelAppearance)
#endif
.SetMethod("isInvertedColorScheme",
&SystemPreferences::IsInvertedColorScheme)

View file

@ -89,6 +89,12 @@ class SystemPreferences : public mate::EventEmitter<SystemPreferences>
mate::Arguments* args);
void RemoveUserDefault(const std::string& name);
bool IsSwipeTrackingFromScrollEventsEnabled();
// TODO(MarshallOfSound): Write tests for these methods once we
// are running tests on a Mojave machine
v8::Local<v8::Value> GetEffectiveAppearance(v8::Isolate* isolate);
v8::Local<v8::Value> GetAppLevelAppearance(v8::Isolate* isolate);
void SetAppLevelAppearance(mate::Arguments* args);
#endif
bool IsDarkMode();
bool IsInvertedColorScheme();

View file

@ -8,13 +8,58 @@
#import <Cocoa/Cocoa.h>
#include "atom/browser/mac/atom_application.h"
#include "atom/browser/mac/dict_util.h"
#include "atom/common/native_mate_converters/gurl_converter.h"
#include "atom/common/native_mate_converters/value_converter.h"
#include "base/strings/sys_string_conversions.h"
#include "base/values.h"
#include "native_mate/object_template_builder.h"
#include "net/base/mac/url_conversions.h"
namespace mate {
template <>
struct Converter<NSAppearance*> {
static bool FromV8(v8::Isolate* isolate,
v8::Local<v8::Value> val,
NSAppearance** out) {
if (val->IsNull()) {
*out = nil;
return true;
}
std::string name;
if (!mate::ConvertFromV8(isolate, val, &name)) {
return false;
}
if (name == "light") {
*out = [NSAppearance appearanceNamed:NSAppearanceNameAqua];
return true;
} else if (name == "dark") {
*out = [NSAppearance appearanceNamed:NSAppearanceNameDarkAqua];
return true;
}
return false;
}
static v8::Local<v8::Value> ToV8(v8::Isolate* isolate, NSAppearance* val) {
if (val == nil) {
return v8::Null(isolate);
}
if (val.name == NSAppearanceNameAqua) {
return mate::ConvertToV8(isolate, "light");
}
if (val.name == NSAppearanceNameDarkAqua) {
return mate::ConvertToV8(isolate, "dark");
}
return mate::ConvertToV8(isolate, "unknown");
}
};
} // namespace mate
namespace atom {
namespace api {
@ -323,6 +368,35 @@ bool SystemPreferences::IsSwipeTrackingFromScrollEventsEnabled() {
return [NSEvent isSwipeTrackingFromScrollEventsEnabled];
}
v8::Local<v8::Value> SystemPreferences::GetEffectiveAppearance(
v8::Isolate* isolate) {
if (@available(macOS 10.14, *)) {
return mate::ConvertToV8(
isolate, [NSApplication sharedApplication].effectiveAppearance);
}
return v8::Null(isolate);
}
v8::Local<v8::Value> SystemPreferences::GetAppLevelAppearance(
v8::Isolate* isolate) {
if (@available(macOS 10.14, *)) {
return mate::ConvertToV8(isolate,
[NSApplication sharedApplication].appearance);
}
return v8::Null(isolate);
}
void SystemPreferences::SetAppLevelAppearance(mate::Arguments* args) {
if (@available(macOS 10.14, *)) {
NSAppearance* appearance;
if (args->GetNext(&appearance)) {
[[NSApplication sharedApplication] setAppearance:appearance];
} else {
args->ThrowError("Invalid app appearance provided as first argument");
}
}
}
} // namespace api
} // namespace atom

View file

@ -6,6 +6,21 @@
#include "base/mac/scoped_nsobject.h"
#include "base/mac/scoped_sending_event.h"
// Forward Declare Appareance APIs
@interface NSApplication (HighSierraSDK)
@property(copy, readonly)
NSAppearance* effectiveAppearance API_AVAILABLE(macosx(10.14));
@property(copy, readonly) NSAppearance* appearance API_AVAILABLE(macosx(10.14));
- (void)setAppearance:(NSAppearance*)appearance API_AVAILABLE(macosx(10.14));
@end
extern "C" {
#if !defined(MAC_OS_X_VERSION_10_14) || \
MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_14
BASE_EXPORT extern NSString* const NSAppearanceNameDarkAqua;
#endif // MAC_OS_X_VERSION_10_14
} // extern "C"
@interface AtomApplication : NSApplication <CrAppProtocol,
CrAppControlProtocol,
NSUserActivityDelegate> {

View file

@ -11,6 +11,11 @@
#include "base/strings/sys_string_conversions.h"
#include "content/public/browser/browser_accessibility_state.h"
#if !defined(MAC_OS_X_VERSION_10_14) || \
MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_14
NSString* const NSAppearanceNameDarkAqua = @"NSAppearanceNameDarkAqua";
#endif // MAC_OS_X_VERSION_10_14
namespace {
inline void dispatch_sync_main(dispatch_block_t block) {

View file

@ -1,4 +1,4 @@
const { app, BrowserWindow } = require('electron')
const { app, BrowserWindow, systemPreferences } = require('electron')
const path = require('path')
let mainWindow = null
@ -11,6 +11,10 @@ app.on('window-all-closed', () => {
exports.load = async (appUrl) => {
await app.whenReady()
if (process.platform === 'darwin') {
systemPreferences.startAppLevelAppearanceTrackingOS()
}
const options = {
width: 900,
height: 600,

View file

@ -35,6 +35,14 @@ Returns:
* `invertedColorScheme` Boolean - `true` if an inverted color scheme, such as
a high contrast theme, is being used, `false` otherwise.
### Event: 'appearance-changed' _macOS_
Returns:
* `newAppearance` String - Can be `dark` or `light`
**NOTE:** This event is only emitted after you have called `startAppLevelAppearanceTrackingOS`
## Methods
### `systemPreferences.isDarkMode()` _macOS_
@ -274,3 +282,51 @@ Returns `Boolean` - `true` if an inverted color scheme, such as a high contrast
theme, is active, `false` otherwise.
[windows-colors]:https://msdn.microsoft.com/en-us/library/windows/desktop/ms724371(v=vs.85).aspx
### `systemPreferences.getEffectiveAppearance()` _macOS_
Returns `String` - Can be `dark`, `light` or `unknown`.
Gets the macOS appearance setting that is currently applied to your application,
maps to [NSApplication.effectiveAppearance](https://developer.apple.com/documentation/appkit/nsapplication/2967171-effectiveappearance?language=objc)
Please note that until Electron is built targeting the 10.14 SDK, your application's
`effectiveAppearance` will default to 'light' and won't inherit the OS preference. In
the interim we have provided a helper method `startAppLevelAppearanceTrackingOS()`
which emulates this behavior.
### `systemPreferences.getAppLevelAppearance()` _macOS_
Returns `String` | `null` - Can be `dark`, `light` or `unknown`.
Gets the macOS appearance setting that you have declared you want for
your application, maps to [NSApplication.appearance](https://developer.apple.com/documentation/appkit/nsapplication/2967170-appearance?language=objc).
You can use the `setAppLevelAppearance` API to set this value.
### `systemPreferences.setAppLevelAppearance(appearance)` _macOS_
* `appearance` String | null - Can be `dark` or `light`
Sets the appearance setting for your application, this should override the
system default and override the value of `getEffectiveAppearance`.
### `systemPreferences.startAppLevelAppearanceTrackingOS()` _macOS_
This is a helper method to make your application's "appearance" setting track the
user's OS level appearance setting. I.e. your app will have dark mode enabled if
the user's system has dark mode enabled.
You can track this automatic change with the `appearance-changed` event.
**Note:** This method is exempt from our standard deprecation cycle and will be removed
without deprecation in an upcoming major release of Electron as soon as we target the 10.14
SDK
### `systemPreferences.stopAppLevelAppearanceTrackingOS()` _macOS_
This is a helper method to stop your application tracking the OS level appearance
setting. It is a no-op if you have not called `startAppLevelAppearanceTrackingOS()`
**Note:** This method is exempt from our standard deprecation cycle and will be removed
without deprecation in an upcoming major release of Electron as soon as we target the 10.14
SDK

View file

@ -1,5 +1,6 @@
'use strict'
const { app } = require('electron')
const { EventEmitter } = require('events')
const { systemPreferences, SystemPreferences } = process.atomBinding('system_preferences')
@ -7,4 +8,40 @@ const { systemPreferences, SystemPreferences } = process.atomBinding('system_pre
Object.setPrototypeOf(SystemPreferences.prototype, EventEmitter.prototype)
EventEmitter.call(systemPreferences)
if (process.platform === 'darwin') {
let appearanceTrackingSubscriptionID = null
systemPreferences.startAppLevelAppearanceTrackingOS = () => {
if (appearanceTrackingSubscriptionID !== null) return
const updateAppearanceBasedOnOS = () => {
const newAppearance = systemPreferences.isDarkMode()
? 'dark'
: 'light'
if (systemPreferences.getAppLevelAppearance() !== newAppearance) {
systemPreferences.setAppLevelAppearance(newAppearance)
// TODO(MarshallOfSound): Once we remove this logic and build against 10.14
// SDK we should re-implement this event as a monitor of `effectiveAppearance`
systemPreferences.emit('appearance-changed', newAppearance)
}
}
appearanceTrackingSubscriptionID = systemPreferences.subscribeNotification(
'AppleInterfaceThemeChangedNotification',
updateAppearanceBasedOnOS
)
updateAppearanceBasedOnOS()
}
systemPreferences.stopAppLevelAppearanceTrackingOS = () => {
if (appearanceTrackingSubscriptionID === null) return
systemPreferences.unsubscribeNotification(appearanceTrackingSubscriptionID)
}
app.on('quit', systemPreferences.stopAppLevelAppearanceTrackingOS)
}
module.exports = systemPreferences