Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add media access apis for mojave (backport: 4-0-x) #15948

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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.