From 940ec70f13830dd9feea75fd5ff2900007e5cd1e Mon Sep 17 00:00:00 2001
From: Vitor Kiguchi <vitor-kiguchi@hotmail.com>
Date: Thu, 13 May 2021 02:41:12 -0300
Subject: [PATCH] Apple: request authorization for camera usage

---
 CMakeLists.txt                                |  3 +-
 src/citra_qt/CMakeLists.txt                   |  4 +
 src/citra_qt/camera/qt_multimedia_camera.cpp  |  8 ++
 .../configuration/configure_camera.cpp        | 12 ++-
 src/citra_qt/usage_authorization.h            | 12 +++
 src/citra_qt/usage_authorization.mm           | 79 +++++++++++++++++++
 6 files changed, 116 insertions(+), 2 deletions(-)
 create mode 100644 src/citra_qt/usage_authorization.h
 create mode 100644 src/citra_qt/usage_authorization.mm

diff --git a/CMakeLists.txt b/CMakeLists.txt
index af8caa7fe9..58172fa7b3 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 4f64f7be6d..868c68e4ae 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 f7c3b14d8f..dd932cae04 100644
--- a/src/citra_qt/camera/qt_multimedia_camera.cpp
+++ b/src/citra_qt/camera/qt_multimedia_camera.cpp
@@ -9,6 +9,7 @@
 #include <QThread>
 #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 c11dbca627..d84cac4f2b 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<std::string, 3> ConfigureCamera::Implementations = {
     "blank", /* Blank */
     "image", /* Image */
@@ -46,9 +50,15 @@ ConfigureCamera::~ConfigureCamera() {
 
 void ConfigureCamera::ConnectEvents() {
     connect(ui->image_source,
-            static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, [this] {
+            static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
+            [this](int index) {
                 StopPreviewing();
                 UpdateImageSourceUI();
+#if defined(__APPLE__)
+                if (index == 2) {
+                    AppleAuthorization::CheckAuthorizationForCamera();
+                }
+#endif
             });
     connect(ui->camera_selection,
             static_cast<void (QComboBox::*)(int)>(&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 0000000000..e9be685b6f
--- /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 0000000000..e9afb0748d
--- /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 <AVFoundation/AVFoundation.h>
+
+#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