Skip to content

Commit

Permalink
feat: add APIs to support mojave dark mode
Browse files Browse the repository at this point in the history
Closes #13387
  • Loading branch information
MarshallOfSound committed Sep 21, 2018
1 parent 32a9df2 commit 79e0ba4
Show file tree
Hide file tree
Showing 9 changed files with 188 additions and 2 deletions.
6 changes: 6 additions & 0 deletions atom/browser/api/atom_api_system_preferences.cc
Expand Up @@ -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)
Expand Down
6 changes: 6 additions & 0 deletions atom/browser/api/atom_api_system_preferences.h
Expand Up @@ -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();
Expand Down
73 changes: 73 additions & 0 deletions atom/browser/api/atom_api_system_preferences_mac.mm
Expand Up @@ -8,13 +8,57 @@

#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;
} else {
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;
}
return true;
}

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");
} else if (val.name == NSAppearanceNameDarkAqua) {
return mate::ConvertToV8(isolate, "dark");
}

return mate::ConvertToV8(isolate, "unknown");
}
};
} // namespace mate

namespace atom {

namespace api {
Expand Down Expand Up @@ -323,6 +367,35 @@
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
15 changes: 15 additions & 0 deletions atom/browser/mac/atom_application.h
Expand Up @@ -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> {
Expand Down
5 changes: 5 additions & 0 deletions atom/browser/mac/atom_application.mm
Expand Up @@ -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) {
Expand Down
4 changes: 3 additions & 1 deletion default_app/default_app.js
@@ -1,4 +1,4 @@
const { app, BrowserWindow } = require('electron')
const { app, BrowserWindow, systemPreferences } = require('electron')
const path = require('path')

let mainWindow = null
Expand All @@ -11,6 +11,8 @@ app.on('window-all-closed', () => {
exports.load = async (appUrl) => {
await app.whenReady()

systemPreferences.startAppLevelAppearanceTrackingOS()

const options = {
width: 900,
height: 600,
Expand Down
48 changes: 48 additions & 0 deletions docs/api/system-preferences.md
Expand Up @@ -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_
Expand Down Expand Up @@ -274,3 +282,43 @@ 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.effectiverAppearance](https://developer.apple.com/documentation/appkit/nsapplication/2967171-effectiveappearance?language=objc)

Please note that because Electron is not built targetting the 10.14 SDK your applications
`effectiveAppearance` will always default to "light" and never automatically inherit the OS
level setting. We have provided a helper method `startAppLevelAppearanceTrackingOS()` which
will emulate this behavior until we start targetting the 10.14 SDK.

### `systemPreferences.getAppLevelAppearance()` _macOS_

Returns `String` | `null` - Can be `dark`, `light` or `unknown`.

Gets the macOS appearance setting that is 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 applications "appearance" setting track the
users OS level appearance setting. I.e. Your app will have dark mode enabled if
the users system has dark mode enabled.

You can track this automatic change with the `appearance-changed` event.

### `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()`
2 changes: 1 addition & 1 deletion lib/browser/api/browser-window.js
Expand Up @@ -141,7 +141,7 @@ BrowserWindow.fromId = (id) => {
}

BrowserWindow.getAllWindows = () => {
return TopLevelWindow.getAllWindows().filter(isBrowserWindow)
return TopLevelWindow.getAllWindows()
}

BrowserWindow.getFocusedWindow = () => {
Expand Down
31 changes: 31 additions & 0 deletions lib/browser/api/system-preferences.js
@@ -1,8 +1,39 @@
const { app } = require('electron')
const { EventEmitter } = require('events')
const { systemPreferences, SystemPreferences } = process.atomBinding('system_preferences')

// SystemPreferences is an EventEmitter.
Object.setPrototypeOf(SystemPreferences.prototype, EventEmitter.prototype)
EventEmitter.call(systemPreferences)

let appearanceTrackingSubscriptionID = null

systemPreferences.startAppLevelAppearanceTrackingOS = () => {
if (appearanceTrackingSubscriptionID !== null) return

const updateAppearanceBasedOnOS = () => {
const newAppearance = systemPreferences.isDarkMode()
? 'dark'
: 'light'

systemPreferences.emit('appearance-changed', newAppearance)
systemPreferences.setAppLevelAppearance(newAppearance)
}

appearanceTrackingSubscriptionID = systemPreferences.subscribeNotification(
'AppleInterfaceThemeChangedNotification',
updateAppearanceBasedOnOS
)

updateAppearanceBasedOnOS()
}

systemPreferences.stopAppLevelAppearanceTrackingOS = () => {
if (appearanceTrackingSubscriptionID === null) return

systemPreferences.unsubscribeNotification(appearanceTrackingSubscriptionID)
}

app.on('quit', systemPreferences.stopAppLevelAppearanceTrackingOS)

module.exports = systemPreferences

0 comments on commit 79e0ba4

Please sign in to comment.