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:
parent
8963529238
commit
0d2a0c7583
8 changed files with 204 additions and 1 deletions
|
@ -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)
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue