From bfb6a5b5de1e7a89ceebabe33e792d5c03a2cf46 Mon Sep 17 00:00:00 2001 From: Steveice10 <1269164+Steveice10@users.noreply.github.com> Date: Mon, 19 Jun 2023 15:50:26 -0700 Subject: [PATCH] common: Add C++ version of Apple authorization logic. (#6616) --- CMakeLists.txt | 17 +--- src/citra_qt/CMakeLists.txt | 7 +- src/citra_qt/camera/qt_multimedia_camera.cpp | 2 +- .../configuration/configure_audio.cpp | 2 +- .../configuration/configure_camera.cpp | 5 +- src/citra_qt/macos_authorization.mm | 93 ------------------- src/citra_qt/main.cpp | 2 +- src/common/CMakeLists.txt | 7 ++ src/common/apple_authorization.cpp | 83 +++++++++++++++++ .../apple_authorization.h} | 2 +- 10 files changed, 104 insertions(+), 116 deletions(-) delete mode 100644 src/citra_qt/macos_authorization.mm create mode 100644 src/common/apple_authorization.cpp rename src/{citra_qt/macos_authorization.h => common/apple_authorization.h} (85%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 81b956070..7501fdb88 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,14 +13,11 @@ include(CMakeDependentOption) project(citra LANGUAGES C CXX ASM) -if (APPLE) - enable_language(OBJC) - if (IOS) - # Enable searching CMAKE_PREFIX_PATH for bundled dependencies. - set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY BOTH) - set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE BOTH) - set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE BOTH) - endif() +if (IOS) + # Enable searching CMAKE_PREFIX_PATH for bundled dependencies. + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY BOTH) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE BOTH) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE BOTH) endif() 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") set(CITRA_USE_PRECOMPILED_HEADERS OFF) endif() - if(APPLE) - message(WARNING "Precompiled Headers currently do not work on Apple. Disabling PCH") - set(CITRA_USE_PRECOMPILED_HEADERS OFF) - endif() endif() if (CITRA_USE_PRECOMPILED_HEADERS) message(STATUS "Using Precompiled Headers.") diff --git a/src/citra_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt index 04d113abb..8b4a0e648 100644 --- a/src/citra_qt/CMakeLists.txt +++ b/src/citra_qt/CMakeLists.txt @@ -254,12 +254,7 @@ if (APPLE) "${DIST_DIR}/LaunchScreen.storyboard" "${DIST_DIR}/launch_logo.png" ) - - target_sources(citra-qt PRIVATE - ${APPLE_RESOURCES} - macos_authorization.h - macos_authorization.mm - ) + target_sources(citra-qt PRIVATE ${APPLE_RESOURCES}) # Define app bundle metadata. include(GenerateBuildInfo) diff --git a/src/citra_qt/camera/qt_multimedia_camera.cpp b/src/citra_qt/camera/qt_multimedia_camera.cpp index 972aa960f..47b9f0f26 100644 --- a/src/citra_qt/camera/qt_multimedia_camera.cpp +++ b/src/citra_qt/camera/qt_multimedia_camera.cpp @@ -10,7 +10,7 @@ #include "citra_qt/main.h" #if defined(__APPLE__) -#include "citra_qt/macos_authorization.h" +#include "common/apple_authorization.h" #endif namespace Camera { diff --git a/src/citra_qt/configuration/configure_audio.cpp b/src/citra_qt/configuration/configure_audio.cpp index 746816a0b..c32bebc19 100644 --- a/src/citra_qt/configuration/configure_audio.cpp +++ b/src/citra_qt/configuration/configure_audio.cpp @@ -14,7 +14,7 @@ #include "ui_configure_audio.h" #if defined(__APPLE__) -#include "citra_qt/macos_authorization.h" +#include "common/apple_authorization.h" #endif ConfigureAudio::ConfigureAudio(QWidget* parent) diff --git a/src/citra_qt/configuration/configure_camera.cpp b/src/citra_qt/configuration/configure_camera.cpp index deb979d2e..de270726e 100644 --- a/src/citra_qt/configuration/configure_camera.cpp +++ b/src/citra_qt/configuration/configure_camera.cpp @@ -16,7 +16,7 @@ #include "ui_configure_camera.h" #if defined(__APPLE__) -#include "citra_qt/macos_authorization.h" +#include "common/apple_authorization.h" #endif const std::array ConfigureCamera::Implementations = { @@ -264,6 +264,9 @@ void ConfigureCamera::SetConfiguration() { } } if (camera_name[index] == "qt") { +#ifdef __APPLE__ + AppleAuthorization::CheckAuthorizationForCamera(); +#endif ui->system_camera->setCurrentIndex(0); if (!camera_config[index].empty()) { ui->system_camera->setCurrentText(QString::fromStdString(camera_config[index])); diff --git a/src/citra_qt/macos_authorization.mm b/src/citra_qt/macos_authorization.mm deleted file mode 100644 index cca44866c..000000000 --- a/src/citra_qt/macos_authorization.mm +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright 2020 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#import - -#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 diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp index ad53376ad..7c857761f 100644 --- a/src/citra_qt/main.cpp +++ b/src/citra_qt/main.cpp @@ -96,7 +96,7 @@ #include "video_core/video_core.h" #ifdef __APPLE__ -#include "macos_authorization.h" +#include "common/apple_authorization.h" #endif #ifdef USE_DISCORD_PRESENCE diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index f9fb13efb..14a21b867 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -138,6 +138,13 @@ add_library(citra_common STATIC zstd_compression.h ) +if (APPLE) + target_sources(citra_common PUBLIC + apple_authorization.h + apple_authorization.cpp + ) +endif() + if (MSVC) target_compile_options(citra_common PRIVATE /W4 diff --git a/src/common/apple_authorization.cpp b/src/common/apple_authorization.cpp new file mode 100644 index 000000000..b07e20225 --- /dev/null +++ b/src/common/apple_authorization.cpp @@ -0,0 +1,83 @@ +// Copyright 2023 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#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(objc_msgSend)( + objc_getClass("NSString"), sel_registerName("stringWithUTF8String:"), string.data()); +} + +AVAuthorizationStatus GetAuthorizationStatus(AVMediaType media_type) { + return reinterpret_cast(objc_msgSend)( + objc_getClass("AVCaptureDevice"), sel_registerName("authorizationStatusForMediaType:"), + media_type); +} + +void RequestAccess(AVMediaType media_type, void (^callback)(bool)) { + reinterpret_cast(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 authorization_promise; + std::future 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 diff --git a/src/citra_qt/macos_authorization.h b/src/common/apple_authorization.h similarity index 85% rename from src/citra_qt/macos_authorization.h rename to src/common/apple_authorization.h index c9292d1f9..e9b1041dd 100644 --- a/src/citra_qt/macos_authorization.h +++ b/src/common/apple_authorization.h @@ -1,4 +1,4 @@ -// Copyright 2020 Citra Emulator Project +// Copyright 2023 Citra Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included.