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).
master
PatTheMav 2022-07-14 19:22:21 +02:00 committed by Patrick Heyer
parent 731c6f991b
commit e15fdf69c0
4 changed files with 167 additions and 2 deletions

View File

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

View File

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

View File

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

View File

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