Skip to content

Commit

Permalink
feat: add media access APIs for macOS Mojave (#15948)
Browse files Browse the repository at this point in the history
  • Loading branch information
trop[bot] authored and codebytere committed Dec 7, 2018
1 parent 4ad3a39 commit 3bd1243
Show file tree
Hide file tree
Showing 6 changed files with 121 additions and 2 deletions.
1 change: 1 addition & 0 deletions BUILD.gn
Expand Up @@ -558,6 +558,7 @@ if (is_mac) {
sources = filenames.framework_sources

libs = [
"AVFoundation.framework",
"Carbon.framework",
"QuartzCore.framework",
"Quartz.framework",
Expand Down
3 changes: 3 additions & 0 deletions atom/browser/api/atom_api_system_preferences.cc
Expand Up @@ -83,6 +83,9 @@ void SystemPreferences::BuildPrototype(
&SystemPreferences::GetAppLevelAppearance)
.SetMethod("setAppLevelAppearance",
&SystemPreferences::SetAppLevelAppearance)
.SetMethod("getMediaAccessStatus",
&SystemPreferences::GetMediaAccessStatus)
.SetMethod("askForMediaAccess", &SystemPreferences::AskForMediaAccess)
#endif
.SetMethod("isInvertedColorScheme",
&SystemPreferences::IsInvertedColorScheme)
Expand Down
8 changes: 8 additions & 0 deletions atom/browser/api/atom_api_system_preferences.h
Expand Up @@ -9,6 +9,7 @@
#include <string>

#include "atom/browser/api/event_emitter.h"
#include "atom/common/promise_util.h"
#include "base/callback.h"
#include "base/values.h"
#include "native_mate/handle.h"
Expand Down Expand Up @@ -90,6 +91,13 @@ class SystemPreferences : public mate::EventEmitter<SystemPreferences>
void RemoveUserDefault(const std::string& name);
bool IsSwipeTrackingFromScrollEventsEnabled();

// TODO(codebytere): Write tests for these methods once we
// are running tests on a Mojave machine
std::string GetMediaAccessStatus(const std::string& media_type,
mate::Arguments* args);
v8::Local<v8::Promise> AskForMediaAccess(v8::Isolate* isolate,
const std::string& media_type);

// TODO(MarshallOfSound): Write tests for these methods once we
// are running tests on a Mojave machine
v8::Local<v8::Value> GetEffectiveAppearance(v8::Isolate* isolate);
Expand Down
67 changes: 67 additions & 0 deletions atom/browser/api/atom_api_system_preferences_mac.mm
Expand Up @@ -6,6 +6,7 @@

#include <map>

#import <AVFoundation/AVFoundation.h>
#import <Cocoa/Cocoa.h>

#include "atom/browser/mac/atom_application.h"
Expand Down Expand Up @@ -78,6 +79,31 @@ static bool FromV8(v8::Isolate* isolate,
// The map to convert |id| to |int|.
std::map<int, id> g_id_map;

AVMediaType ParseMediaType(const std::string& media_type) {
if (media_type == "camera") {
return AVMediaTypeVideo;
} else if (media_type == "microphone") {
return AVMediaTypeAudio;
} else {
return nil;
}
}

std::string ConvertAuthorizationStatus(AVAuthorizationStatusMac status) {
switch (status) {
case AVAuthorizationStatusNotDeterminedMac:
return "not-determined";
case AVAuthorizationStatusRestrictedMac:
return "restricted";
case AVAuthorizationStatusDeniedMac:
return "denied";
case AVAuthorizationStatusAuthorizedMac:
return "granted";
default:
return "unknown";
}
}

} // namespace

void SystemPreferences::PostNotification(
Expand Down Expand Up @@ -360,6 +386,47 @@ static bool FromV8(v8::Isolate* isolate,
}
}

std::string SystemPreferences::GetMediaAccessStatus(
const std::string& media_type,
mate::Arguments* args) {
if (auto type = ParseMediaType(media_type)) {
if (@available(macOS 10.14, *)) {
return ConvertAuthorizationStatus(
[AVCaptureDevice authorizationStatusForMediaType:type]);
} else {
// access always allowed pre-10.14 Mojave
return ConvertAuthorizationStatus(AVAuthorizationStatusAuthorizedMac);
}
} else {
args->ThrowError("Invalid media type");
return std::string();
}
}

v8::Local<v8::Promise> SystemPreferences::AskForMediaAccess(
v8::Isolate* isolate,
const std::string& media_type) {
scoped_refptr<util::Promise> promise = new util::Promise(isolate);

if (auto type = ParseMediaType(media_type)) {
if (@available(macOS 10.14, *)) {
[AVCaptureDevice requestAccessForMediaType:type
completionHandler:^(BOOL granted) {
dispatch_async(dispatch_get_main_queue(), ^{
promise->Resolve(!!granted);
});
}];
} else {
// access always allowed pre-10.14 Mojave
promise->Resolve(true);
}
} else {
promise->RejectWithErrorMessage("Invalid media type");
}

return promise->GetHandle();
}

void SystemPreferences::RemoveUserDefault(const std::string& name) {
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
[defaults removeObjectForKey:base::SysUTF8ToNSString(name)];
Expand Down
25 changes: 24 additions & 1 deletion atom/browser/mac/atom_application.h
Expand Up @@ -6,14 +6,37 @@
#include "base/mac/scoped_nsobject.h"
#include "base/mac/scoped_sending_event.h"

// Forward Declare Appareance APIs
#import <AVFoundation/AVFoundation.h>

// Forward Declare Appearance 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

// forward declare Access APIs
typedef NSString* AVMediaType NS_EXTENSIBLE_STRING_ENUM;

AVF_EXPORT AVMediaType const AVMediaTypeVideo;
AVF_EXPORT AVMediaType const AVMediaTypeAudio;

typedef NS_ENUM(NSInteger, AVAuthorizationStatusMac) {
AVAuthorizationStatusNotDeterminedMac = 0,
AVAuthorizationStatusRestrictedMac = 1,
AVAuthorizationStatusDeniedMac = 2,
AVAuthorizationStatusAuthorizedMac = 3,
};

@interface AVCaptureDevice (MojaveSDK)
+ (void)requestAccessForMediaType:(AVMediaType)mediaType
completionHandler:(void (^)(BOOL granted))handler
API_AVAILABLE(macosx(10.14));
+ (AVAuthorizationStatusMac)authorizationStatusForMediaType:
(AVMediaType)mediaType 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
Expand Down
19 changes: 18 additions & 1 deletion docs/api/system-preferences.md
Expand Up @@ -302,7 +302,6 @@ using `electron-packager` or `electron-forge` just set the `enableDarwinDarkMode
packager option to `true`. See the [Electron Packager API](https://github.com/electron-userland/electron-packager/blob/master/docs/api.md#darwindarkmodesupport)
for more details.


### `systemPreferences.getAppLevelAppearance()` _macOS_

Returns `String` | `null` - Can be `dark`, `light` or `unknown`.
Expand All @@ -317,3 +316,21 @@ You can use the `setAppLevelAppearance` API to set this value.

Sets the appearance setting for your application, this should override the
system default and override the value of `getEffectiveAppearance`.

### `systemPreferences.getMediaAccessStatus(mediaType)` _macOS_

* `mediaType` String - `microphone` or `camera`.

Returns `String` - Can be `not-determined`, `granted`, `denied`, `restricted` or `unknown`.

This user consent was not required until macOS 10.14 Mojave, so this method will always return `granted` if your system is running 10.13 High Sierra or lower.

### `systemPreferences.askForMediaAccess(mediaType)` _macOS_

* `mediaType` String - the type of media being requested; can be `microphone`, `camera`.

Returns `Promise<Boolean>` - A promise that resolves with `true` if consent was granted and `false` if it was denied. If an invalid `mediaType` is passed, the promise will be rejected. If an access request was denied and later is changed through the System Preferences pane, a restart of the app will be required for the new permissions to take effect. If access has already been requested and denied, it _must_ be changed through the preference pane; an alert will not pop up and the promise will resolve with the existing access status.

**Important:** In order to properly leverage this API, you [must set](https://developer.apple.com/documentation/avfoundation/cameras_and_media_capture/requesting_authorization_for_media_capture_on_macos?language=objc) the `NSMicrophoneUsageDescription` and `NSCameraUsageDescription` strings in your app's `Info.plist` file. The values for these keys will be used to populate the permission dialogs so that the user will be properly informed as to the purpose of the permission request. See [Electron Application Distribution](https://electronjs.org/docs/tutorial/application-distribution#macos) for more information about how to set these in the context of Electron.

This user consent was not required until macOS 10.14 Mojave, so this method will always return `true` if your system is running 10.13 High Sierra or lower.

0 comments on commit 3bd1243

Please sign in to comment.