Browse Source

feat: add media access APIs for macOS Mojave (#15948)

trop[bot] 6 years ago
parent
commit
3bd1243aed

+ 1 - 0
BUILD.gn

@@ -558,6 +558,7 @@ if (is_mac) {
     sources = filenames.framework_sources
 
     libs = [
+      "AVFoundation.framework",
       "Carbon.framework",
       "QuartzCore.framework",
       "Quartz.framework",

+ 3 - 0
atom/browser/api/atom_api_system_preferences.cc

@@ -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)

+ 8 - 0
atom/browser/api/atom_api_system_preferences.h

@@ -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"
@@ -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);

+ 67 - 0
atom/browser/api/atom_api_system_preferences_mac.mm

@@ -6,6 +6,7 @@
 
 #include <map>
 
+#import <AVFoundation/AVFoundation.h>
 #import <Cocoa/Cocoa.h>
 
 #include "atom/browser/mac/atom_application.h"
@@ -78,6 +79,31 @@ int g_next_id = 0;
 // 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(
@@ -360,6 +386,47 @@ void SystemPreferences::SetUserDefault(const std::string& name,
   }
 }
 
+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)];

+ 24 - 1
atom/browser/mac/atom_application.h

@@ -6,7 +6,9 @@
 #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));
@@ -14,6 +16,27 @@
 - (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

+ 18 - 1
docs/api/system-preferences.md

@@ -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`.
@@ -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.