浏览代码

UI: Add functions to check for and request macOS permissions

Adds functions to check and/or request specific macOS permissions
(audio device access, video device access, accessibility access, and
screen capture access).

By default only audio capture, video capture, and accessibility are
requested on launch - the first two have straight-forward "Yes/No"
prompts, the latter requires people to enable OBS in the settings
application (but is required for hotkey functionality, independent
of scene setups).
PatTheMav 3 年之前
父节点
当前提交
e15fdf69c0
共有 4 个文件被更改,包括 167 次插入2 次删除
  1. 5 2
      UI/CMakeLists.txt
  2. 9 0
      UI/obs-app.cpp
  3. 134 0
      UI/platform-osx.mm
  4. 19 0
      UI/platform.hpp

+ 5 - 2
UI/CMakeLists.txt

@@ -417,9 +417,12 @@ elseif(OS_MACOS)
   endif()
 
   find_library(APPKIT Appkit)
-  mark_as_advanced(APPKIT)
+  find_library(AVFOUNDATION AVFoundation)
+  find_library(APPLICATIONSERVICES ApplicationServices)
+  mark_as_advanced(APPKIT AVFOUNDATION APPLICATIONSERVICES)
 
-  target_link_libraries(obs PRIVATE ${APPKIT})
+  target_link_libraries(obs PRIVATE ${APPKIT} ${AVFOUNDATION}
+                                    ${APPLICATIONSERVICES})
 
   if(ENABLE_SPARKLE_UPDATER)
     find_library(SPARKLE Sparkle)

+ 9 - 0
UI/obs-app.cpp

@@ -2185,6 +2185,15 @@ static int run_program(fstream &logFile, int argc, char *argv[])
 		}
 
 #ifdef __APPLE__
+		MacPermissionStatus audio_permission =
+			CheckPermission(kAudioDeviceAccess);
+		MacPermissionStatus video_permission =
+			CheckPermission(kVideoDeviceAccess);
+		MacPermissionStatus accessibility_permission =
+			CheckPermission(kAccessibility);
+		MacPermissionStatus screen_permission =
+			CheckPermission(kScreenCapture);
+
 		bool rosettaTranslated = os_get_emulation_status();
 		blog(LOG_INFO, "Rosetta translation used: %s",
 		     rosettaTranslated ? "true" : "false");

+ 134 - 0
UI/platform-osx.mm

@@ -18,6 +18,7 @@
 #include <sstream>
 #include <dlfcn.h>
 #include <util/base.h>
+#include <util/threading.h>
 #include <obs-config.h>
 #include "platform.hpp"
 #include "obs-app.hpp"
@@ -26,6 +27,9 @@
 #include <sys/sysctl.h>
 
 #import <AppKit/AppKit.h>
+#import <CoreFoundation/CoreFoundation.h>
+#import <AVFoundation/AVFoundation.h>
+#import <ApplicationServices/ApplicationServices.h>
 
 using namespace std;
 
@@ -238,6 +242,136 @@ void EnableOSXDockIcon(bool enable)
 }
 @end
 
+MacPermissionStatus CheckPermissionWithPrompt(MacPermissionType type,
+					      bool prompt_for_permission)
+{
+	__block MacPermissionStatus permissionResponse =
+		kPermissionNotDetermined;
+
+	switch (type) {
+	case kAudioDeviceAccess: {
+		if (@available(macOS 10.14, *)) {
+			AVAuthorizationStatus audioStatus = [AVCaptureDevice
+				authorizationStatusForMediaType:AVMediaTypeAudio];
+
+			if (audioStatus == AVAuthorizationStatusNotDetermined &&
+			    prompt_for_permission) {
+				os_event_t *block_finished;
+				os_event_init(&block_finished,
+					      OS_EVENT_TYPE_MANUAL);
+				[AVCaptureDevice
+					requestAccessForMediaType:AVMediaTypeAudio
+						completionHandler:^(
+							BOOL granted
+							__attribute((unused))) {
+							os_event_signal(
+								block_finished);
+						}];
+				os_event_wait(block_finished);
+				os_event_destroy(block_finished);
+				audioStatus = [AVCaptureDevice
+					authorizationStatusForMediaType:
+						AVMediaTypeAudio];
+			}
+
+			permissionResponse = (MacPermissionStatus)audioStatus;
+		} else {
+			permissionResponse = kPermissionAuthorized;
+		}
+
+		blog(LOG_INFO, "[macOS] Permission for audio device access %s.",
+		     permissionResponse == kPermissionAuthorized ? "granted"
+								 : "denied");
+
+		break;
+	}
+	case kVideoDeviceAccess: {
+		if (@available(macOS 10.14, *)) {
+			AVAuthorizationStatus videoStatus = [AVCaptureDevice
+				authorizationStatusForMediaType:AVMediaTypeVideo];
+
+			if (videoStatus == AVAuthorizationStatusNotDetermined &&
+			    prompt_for_permission) {
+				os_event_t *block_finished;
+				os_event_init(&block_finished,
+					      OS_EVENT_TYPE_MANUAL);
+				[AVCaptureDevice
+					requestAccessForMediaType:AVMediaTypeVideo
+						completionHandler:^(
+							BOOL granted
+							__attribute((unused))) {
+							os_event_signal(
+								block_finished);
+						}];
+
+				os_event_wait(block_finished);
+				os_event_destroy(block_finished);
+				videoStatus = [AVCaptureDevice
+					authorizationStatusForMediaType:
+						AVMediaTypeVideo];
+			}
+
+			permissionResponse = (MacPermissionStatus)videoStatus;
+		} else {
+			permissionResponse = kPermissionAuthorized;
+		}
+
+		blog(LOG_INFO, "[macOS] Permission for video device access %s.",
+		     permissionResponse == kPermissionAuthorized ? "granted"
+								 : "denied");
+
+		break;
+	}
+	case kScreenCapture: {
+		if (@available(macOS 10.15, *)) {
+			permissionResponse = (CGPreflightScreenCaptureAccess()
+						      ? kPermissionAuthorized
+						      : kPermissionDenied);
+
+			if (permissionResponse != kPermissionAuthorized &&
+			    prompt_for_permission) {
+				permissionResponse =
+					(CGRequestScreenCaptureAccess()
+						 ? kPermissionAuthorized
+						 : kPermissionDenied);
+			}
+
+		} else {
+			permissionResponse = kPermissionAuthorized;
+		}
+
+		blog(LOG_INFO, "[macOS] Permission for screen capture %s.",
+		     permissionResponse == kPermissionAuthorized ? "granted"
+								 : "denied");
+
+		break;
+	}
+	case kAccessibility: {
+		permissionResponse = (AXIsProcessTrusted()
+					      ? kPermissionAuthorized
+					      : kPermissionDenied);
+
+		if (permissionResponse != kPermissionAuthorized &&
+		    prompt_for_permission) {
+			NSDictionary *options = @{
+				(__bridge id)kAXTrustedCheckOptionPrompt: @YES
+			};
+			permissionResponse = (AXIsProcessTrustedWithOptions(
+						      (CFDictionaryRef)options)
+						      ? kPermissionAuthorized
+						      : kPermissionDenied);
+		}
+
+		blog(LOG_INFO, "[macOS] Permission for accessibility %s.",
+		     permissionResponse == kPermissionAuthorized ? "granted"
+								 : "denied");
+		break;
+	}
+	}
+
+	return permissionResponse;
+}
+
 void TaskbarOverlayInit() {}
 void TaskbarOverlaySetStatus(TaskbarOverlayStatus status)
 {

+ 19 - 0
UI/platform.hpp

@@ -80,10 +80,29 @@ bool IsRunningOnWine();
 #endif
 
 #ifdef __APPLE__
+typedef enum {
+	kAudioDeviceAccess = 0,
+	kVideoDeviceAccess = 1,
+	kScreenCapture = 2,
+	kAccessibility = 3
+} MacPermissionType;
+
+typedef enum {
+	kPermissionNotDetermined = 0,
+	kPermissionRestricted = 1,
+	kPermissionDenied = 2,
+	kPermissionAuthorized = 3,
+} MacPermissionStatus;
+
 void EnableOSXVSync(bool enable);
 void EnableOSXDockIcon(bool enable);
 bool isInBundle();
 void InstallNSApplicationSubclass();
 void InstallNSThreadLocks();
 void disableColorSpaceConversion(QWidget *window);
+
+MacPermissionStatus CheckPermissionWithPrompt(MacPermissionType type,
+					      bool prompt_for_permission);
+#define CheckPermission(x) CheckPermissionWithPrompt(x, false)
+#define RequestPermission(x) CheckPermissionWithPrompt(x, true)
 #endif