From e15fdf69c0256bd8dc25657e9dd2b251fe22551a Mon Sep 17 00:00:00 2001 From: PatTheMav Date: Thu, 14 Jul 2022 19:22:21 +0200 Subject: [PATCH] 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). --- UI/CMakeLists.txt | 7 ++- UI/obs-app.cpp | 9 +++ UI/platform-osx.mm | 134 +++++++++++++++++++++++++++++++++++++++++++++ UI/platform.hpp | 19 +++++++ 4 files changed, 167 insertions(+), 2 deletions(-) diff --git a/UI/CMakeLists.txt b/UI/CMakeLists.txt index 0136527cc..da127fa56 100644 --- a/UI/CMakeLists.txt +++ b/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) diff --git a/UI/obs-app.cpp b/UI/obs-app.cpp index d058c0ee4..aff2c5dcc 100644 --- a/UI/obs-app.cpp +++ b/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"); diff --git a/UI/platform-osx.mm b/UI/platform-osx.mm index 23b4d28e5..0294af97d 100644 --- a/UI/platform-osx.mm +++ b/UI/platform-osx.mm @@ -18,6 +18,7 @@ #include #include #include +#include #include #include "platform.hpp" #include "obs-app.hpp" @@ -26,6 +27,9 @@ #include #import +#import +#import +#import 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) { diff --git a/UI/platform.hpp b/UI/platform.hpp index f65b31ca7..a7ed1786c 100644 --- a/UI/platform.hpp +++ b/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