common: Add C++ version of Apple authorization logic. (#6616)

This commit is contained in:
Steveice10 2023-06-19 15:50:26 -07:00 committed by GitHub
parent 03dbdfc12f
commit bfb6a5b5de
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 104 additions and 116 deletions

View file

@ -13,15 +13,12 @@ include(CMakeDependentOption)
project(citra LANGUAGES C CXX ASM) project(citra LANGUAGES C CXX ASM)
if (APPLE)
enable_language(OBJC)
if (IOS) if (IOS)
# Enable searching CMAKE_PREFIX_PATH for bundled dependencies. # Enable searching CMAKE_PREFIX_PATH for bundled dependencies.
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY BOTH) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY BOTH)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE BOTH) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE BOTH)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE BOTH) set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE BOTH)
endif() endif()
endif()
option(ENABLE_LTO "Enable link time optimization" OFF) option(ENABLE_LTO "Enable link time optimization" OFF)
@ -73,10 +70,6 @@ if (CITRA_USE_PRECOMPILED_HEADERS)
message(WARNING "Buildcache does not properly support Precompiled Headers. Disabling PCH") message(WARNING "Buildcache does not properly support Precompiled Headers. Disabling PCH")
set(CITRA_USE_PRECOMPILED_HEADERS OFF) set(CITRA_USE_PRECOMPILED_HEADERS OFF)
endif() endif()
if(APPLE)
message(WARNING "Precompiled Headers currently do not work on Apple. Disabling PCH")
set(CITRA_USE_PRECOMPILED_HEADERS OFF)
endif()
endif() endif()
if (CITRA_USE_PRECOMPILED_HEADERS) if (CITRA_USE_PRECOMPILED_HEADERS)
message(STATUS "Using Precompiled Headers.") message(STATUS "Using Precompiled Headers.")

View file

@ -254,12 +254,7 @@ if (APPLE)
"${DIST_DIR}/LaunchScreen.storyboard" "${DIST_DIR}/LaunchScreen.storyboard"
"${DIST_DIR}/launch_logo.png" "${DIST_DIR}/launch_logo.png"
) )
target_sources(citra-qt PRIVATE ${APPLE_RESOURCES})
target_sources(citra-qt PRIVATE
${APPLE_RESOURCES}
macos_authorization.h
macos_authorization.mm
)
# Define app bundle metadata. # Define app bundle metadata.
include(GenerateBuildInfo) include(GenerateBuildInfo)

View file

@ -10,7 +10,7 @@
#include "citra_qt/main.h" #include "citra_qt/main.h"
#if defined(__APPLE__) #if defined(__APPLE__)
#include "citra_qt/macos_authorization.h" #include "common/apple_authorization.h"
#endif #endif
namespace Camera { namespace Camera {

View file

@ -14,7 +14,7 @@
#include "ui_configure_audio.h" #include "ui_configure_audio.h"
#if defined(__APPLE__) #if defined(__APPLE__)
#include "citra_qt/macos_authorization.h" #include "common/apple_authorization.h"
#endif #endif
ConfigureAudio::ConfigureAudio(QWidget* parent) ConfigureAudio::ConfigureAudio(QWidget* parent)

View file

@ -16,7 +16,7 @@
#include "ui_configure_camera.h" #include "ui_configure_camera.h"
#if defined(__APPLE__) #if defined(__APPLE__)
#include "citra_qt/macos_authorization.h" #include "common/apple_authorization.h"
#endif #endif
const std::array<std::string, 3> ConfigureCamera::Implementations = { const std::array<std::string, 3> ConfigureCamera::Implementations = {
@ -264,6 +264,9 @@ void ConfigureCamera::SetConfiguration() {
} }
} }
if (camera_name[index] == "qt") { if (camera_name[index] == "qt") {
#ifdef __APPLE__
AppleAuthorization::CheckAuthorizationForCamera();
#endif
ui->system_camera->setCurrentIndex(0); ui->system_camera->setCurrentIndex(0);
if (!camera_config[index].empty()) { if (!camera_config[index].empty()) {
ui->system_camera->setCurrentText(QString::fromStdString(camera_config[index])); ui->system_camera->setCurrentText(QString::fromStdString(camera_config[index]));

View file

@ -1,93 +0,0 @@
// Copyright 2020 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#import <AVFoundation/AVFoundation.h>
#include "citra_qt/macos_authorization.h"
#include "common/logging/log.h"
namespace AppleAuthorization {
static bool authorized_camera = false;
static bool authorized_microphone = false;
static bool authorized = false;
enum class AuthMediaType { Camera, Microphone };
// Based on
// https://developer.apple.com/documentation/avfoundation/cameras_and_media_capture/requesting_authorization_for_media_capture_on_macos
// TODO: This could be rewritten to return the authorization state, having pure c++ code deal with
// it, log information and possibly wait for the camera access request.
void CheckAuthorization(AuthMediaType type) {
authorized = false;
if (@available(macOS 10.14, *)) {
NSString* media_type;
if (type == AuthMediaType::Camera) {
media_type = AVMediaTypeVideo;
} else {
media_type = AVMediaTypeAudio;
}
// Request permission to access the camera and microphone.
switch ([AVCaptureDevice authorizationStatusForMediaType:media_type]) {
case AVAuthorizationStatusAuthorized:
// The user has previously granted access to the camera.
authorized = true;
break;
case AVAuthorizationStatusNotDetermined: {
// The app hasn't yet asked the user for camera access.
[AVCaptureDevice requestAccessForMediaType:media_type
completionHandler:^(BOOL granted) {
authorized = granted;
}];
if (type == AuthMediaType::Camera) {
LOG_INFO(Frontend, "Camera access requested.");
} else { // AuthMediaType::Microphone
LOG_INFO(Frontend, "Microphone access requested.");
}
break;
}
case AVAuthorizationStatusDenied: {
// The user has previously denied access.
authorized = false;
if (type == AuthMediaType::Camera) {
LOG_WARNING(Frontend, "Camera access denied. To change this you may modify the "
"macOS system permission settings "
"for Citra at 'System Preferences -> Security & Privacy'");
} else { // AuthMediaType::Microphone
LOG_WARNING(Frontend, "Microphone access denied. To change this you may modify the "
"macOS system permission settings "
"for Citra at 'System Preferences -> Security & Privacy'");
}
return;
}
case AVAuthorizationStatusRestricted: {
// The user can't grant access due to restrictions.
authorized = false;
return;
}
}
} else {
authorized = true;
}
}
bool CheckAuthorizationForCamera() {
if (!authorized_camera) {
CheckAuthorization(AuthMediaType::Camera);
authorized_camera = authorized;
}
return authorized_camera;
}
bool CheckAuthorizationForMicrophone() {
if (!authorized_microphone) {
CheckAuthorization(AuthMediaType::Microphone);
authorized_microphone = authorized;
}
return authorized_microphone;
}
} // namespace AppleAuthorization

View file

@ -96,7 +96,7 @@
#include "video_core/video_core.h" #include "video_core/video_core.h"
#ifdef __APPLE__ #ifdef __APPLE__
#include "macos_authorization.h" #include "common/apple_authorization.h"
#endif #endif
#ifdef USE_DISCORD_PRESENCE #ifdef USE_DISCORD_PRESENCE

View file

@ -138,6 +138,13 @@ add_library(citra_common STATIC
zstd_compression.h zstd_compression.h
) )
if (APPLE)
target_sources(citra_common PUBLIC
apple_authorization.h
apple_authorization.cpp
)
endif()
if (MSVC) if (MSVC)
target_compile_options(citra_common PRIVATE target_compile_options(citra_common PRIVATE
/W4 /W4

View file

@ -0,0 +1,83 @@
// Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <future>
#include <objc/message.h>
#include "common/apple_authorization.h"
#include "common/logging/log.h"
namespace AppleAuthorization {
// Bindings to Objective-C APIs
using NSString = void;
using AVMediaType = NSString*;
enum AVAuthorizationStatus : int {
AVAuthorizationStatusNotDetermined = 0,
AVAuthorizationStatusRestricted,
AVAuthorizationStatusDenied,
AVAuthorizationStatusAuthorized,
};
typedef NSString* (*send_stringWithUTF8String)(Class, SEL, const char*);
typedef AVAuthorizationStatus (*send_authorizationStatusForMediaType)(Class, SEL, AVMediaType);
typedef void (*send_requestAccessForMediaType_completionHandler)(Class, SEL, AVMediaType,
void (^callback)(bool));
NSString* StringToNSString(const std::string_view string) {
return reinterpret_cast<send_stringWithUTF8String>(objc_msgSend)(
objc_getClass("NSString"), sel_registerName("stringWithUTF8String:"), string.data());
}
AVAuthorizationStatus GetAuthorizationStatus(AVMediaType media_type) {
return reinterpret_cast<send_authorizationStatusForMediaType>(objc_msgSend)(
objc_getClass("AVCaptureDevice"), sel_registerName("authorizationStatusForMediaType:"),
media_type);
}
void RequestAccess(AVMediaType media_type, void (^callback)(bool)) {
reinterpret_cast<send_requestAccessForMediaType_completionHandler>(objc_msgSend)(
objc_getClass("AVCaptureDevice"),
sel_registerName("requestAccessForMediaType:completionHandler:"), media_type, callback);
}
static AVMediaType AVMediaTypeAudio = StringToNSString("soun");
static AVMediaType AVMediaTypeVideo = StringToNSString("vide");
// Authorization Logic
bool CheckAuthorization(AVMediaType type, const std::string_view& type_name) {
switch (GetAuthorizationStatus(type)) {
case AVAuthorizationStatusNotDetermined: {
LOG_INFO(Frontend, "Requesting {} permission.", type_name);
__block std::promise<bool> authorization_promise;
std::future<bool> authorization_future = authorization_promise.get_future();
RequestAccess(type, ^(bool granted) {
LOG_INFO(Frontend, "{} permission request result: {}", type_name, granted);
authorization_promise.set_value(granted);
});
return authorization_future.get();
}
case AVAuthorizationStatusAuthorized:
return true;
case AVAuthorizationStatusDenied:
LOG_WARNING(Frontend,
"{} permission has been denied and must be enabled via System Settings.",
type_name);
return false;
case AVAuthorizationStatusRestricted:
LOG_WARNING(Frontend, "{} permission is restricted by the system.", type_name);
return false;
}
}
bool CheckAuthorizationForCamera() {
return CheckAuthorization(AVMediaTypeVideo, "Camera");
}
bool CheckAuthorizationForMicrophone() {
return CheckAuthorization(AVMediaTypeAudio, "Microphone");
}
} // namespace AppleAuthorization

View file

@ -1,4 +1,4 @@
// Copyright 2020 Citra Emulator Project // Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.