diff --git a/CMakeLists.txt b/CMakeLists.txt index af8caa7fe..58172fa7b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -244,7 +244,8 @@ endif() if (APPLE) # Umbrella framework for everything GUI-related find_library(COCOA_LIBRARY Cocoa) - set(PLATFORM_LIBRARIES ${COCOA_LIBRARY} ${IOKIT_LIBRARY} ${COREVIDEO_LIBRARY}) + find_library(AVFOUNDATION_LIBRARY AVFoundation) + set(PLATFORM_LIBRARIES ${COCOA_LIBRARY} ${AVFOUNDATION_LIBRARY} ${IOKIT_LIBRARY} ${COREVIDEO_LIBRARY}) elseif (WIN32) # WSAPoll and SHGetKnownFolderPath (AppData/Roaming) didn't exist before WinNT 6.x (Vista) add_definitions(-D_WIN32_WINNT=0x0600 -DWINVER=0x0600) diff --git a/src/citra_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt index 4f64f7be6..868c68e4a 100644 --- a/src/citra_qt/CMakeLists.txt +++ b/src/citra_qt/CMakeLists.txt @@ -236,6 +236,10 @@ if (APPLE) target_sources(citra-qt PRIVATE ${MACOSX_ICON}) set_target_properties(citra-qt PROPERTIES MACOSX_BUNDLE TRUE) set_target_properties(citra-qt PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist) + target_sources(citra-qt PRIVATE + usage_authorization.h + usage_authorization.mm + ) elseif(WIN32) # compile as a win32 gui application instead of a console application target_link_libraries(citra-qt PRIVATE Qt5::WinMain) diff --git a/src/citra_qt/camera/qt_multimedia_camera.cpp b/src/citra_qt/camera/qt_multimedia_camera.cpp index f7c3b14d8..dd932cae0 100644 --- a/src/citra_qt/camera/qt_multimedia_camera.cpp +++ b/src/citra_qt/camera/qt_multimedia_camera.cpp @@ -9,6 +9,7 @@ #include #include "citra_qt/camera/qt_multimedia_camera.h" #include "citra_qt/main.h" +#include "citra_qt/usage_authorization.h" namespace Camera { @@ -187,6 +188,13 @@ void QtMultimediaCameraHandler::StopCamera() { } void QtMultimediaCameraHandler::StartCamera() { +#if defined(__APPLE__) + bool authorized = AppleAuthorization::CheckAuthorizationForCamera(); + if (!authorized) { + LOG_ERROR(Service_CAM, "Unable to start camera due to lack of authorization"); + return; + } +#endif camera->setViewfinderSettings(settings); camera->start(); started = true; diff --git a/src/citra_qt/configuration/configure_camera.cpp b/src/citra_qt/configuration/configure_camera.cpp index c11dbca62..d84cac4f2 100644 --- a/src/citra_qt/configuration/configure_camera.cpp +++ b/src/citra_qt/configuration/configure_camera.cpp @@ -17,6 +17,10 @@ #include "core/settings.h" #include "ui_configure_camera.h" +#if defined(__APPLE__) +#include "citra_qt/usage_authorization.h" +#endif + const std::array ConfigureCamera::Implementations = { "blank", /* Blank */ "image", /* Image */ @@ -46,9 +50,15 @@ ConfigureCamera::~ConfigureCamera() { void ConfigureCamera::ConnectEvents() { connect(ui->image_source, - static_cast(&QComboBox::currentIndexChanged), this, [this] { + static_cast(&QComboBox::currentIndexChanged), this, + [this](int index) { StopPreviewing(); UpdateImageSourceUI(); +#if defined(__APPLE__) + if (index == 2) { + AppleAuthorization::CheckAuthorizationForCamera(); + } +#endif }); connect(ui->camera_selection, static_cast(&QComboBox::currentIndexChanged), this, [this] { diff --git a/src/citra_qt/usage_authorization.h b/src/citra_qt/usage_authorization.h new file mode 100644 index 000000000..e9be685b6 --- /dev/null +++ b/src/citra_qt/usage_authorization.h @@ -0,0 +1,12 @@ +// Copyright 2020 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +namespace AppleAuthorization { + +bool CheckAuthorizationForCamera(); +bool CheckAuthorizationForAudio(); + +} // namespace AppleAuthorization diff --git a/src/citra_qt/usage_authorization.mm b/src/citra_qt/usage_authorization.mm new file mode 100644 index 000000000..e9afb0748 --- /dev/null +++ b/src/citra_qt/usage_authorization.mm @@ -0,0 +1,79 @@ +// Copyright 2020 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#import + +#include "citra_qt/usage_authorization.h" +#include "common/logging/log.h" + +namespace AppleAuthorization { + +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 +void CheckAuthorization(AuthMediaType type) { + 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) { + authorized = true; + }]; + if(type == AuthMediaType::Camera) { + LOG_INFO(Frontend, "Camera 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 settings for Citra at 'System Preferences -> Security & Privacy -> Camera'"); + } + return; + } + case AVAuthorizationStatusRestricted: + { + // The user can't grant access due to restrictions. + authorized = false; + return; + } + } + } + else { + authorized = true; + } +} + +bool CheckAuthorizationForCamera() { + CheckAuthorization(AuthMediaType::Camera); + return authorized; +} + +bool CheckAuthorizationForMicrophone() { + CheckAuthorization(AuthMediaType::Microphone); + return authorized; +} + +} //AppleAuthorization