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
parent
731c6f991b
commit
e15fdf69c0
|
@ -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)
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue