From 5f00747d194722ed4cb321905b464fdd8797ab6b Mon Sep 17 00:00:00 2001 From: Roland Rossgotterer Date: Mon, 6 May 2019 16:14:24 +0200 Subject: [PATCH] [MacOS] Ask user for permission to access microphone This is required for MacOS 10.14 and later. This also requires an explanation why access is required in the apps Info.plist. Hence added custom Info.plist templates for each app. --- src/blackcore/audiodevice.h | 24 +++++ src/blackcore/context/contextaudioimpl.cpp | 105 +++++++++++++++------ src/blackcore/context/contextaudioimpl.h | 12 +++ src/blackmisc/blackmisc.pro | 6 +- src/blackmisc/macos/microphoneaccess.h | 48 ++++++++++ src/blackmisc/macos/microphoneaccess.mm | 62 ++++++++++++ src/swiftcore/Info.plist | 30 ++++++ src/swiftcore/swiftcore.pro | 3 + src/swiftdata/Info.plist | 28 ++++++ src/swiftdata/swiftdata.pro | 3 + src/swiftguistandard/Info.plist | 30 ++++++ src/swiftguistandard/swiftguistandard.pro | 3 + src/swiftlauncher/Info.plist | 28 ++++++ src/swiftlauncher/swiftlauncher.pro | 3 + 14 files changed, 355 insertions(+), 30 deletions(-) create mode 100644 src/blackmisc/macos/microphoneaccess.h create mode 100644 src/blackmisc/macos/microphoneaccess.mm create mode 100644 src/swiftcore/Info.plist create mode 100644 src/swiftdata/Info.plist create mode 100644 src/swiftguistandard/Info.plist create mode 100644 src/swiftlauncher/Info.plist diff --git a/src/blackcore/audiodevice.h b/src/blackcore/audiodevice.h index cb17c2057..04b3d5697 100644 --- a/src/blackcore/audiodevice.h +++ b/src/blackcore/audiodevice.h @@ -43,6 +43,30 @@ namespace BlackCore virtual void setInputDevice(const BlackMisc::Audio::CAudioDeviceInfo &device) = 0; }; + class BLACKCORE_EXPORT CAudioInputDeviceDummy : public IAudioInputDevice + { + Q_OBJECT + public: + //! Constructor + CAudioInputDeviceDummy(QObject *parent = nullptr) : IAudioInputDevice(parent) {} + + //! Destructor + virtual ~CAudioInputDeviceDummy() override = default; + + //! \copydoc IAudioInputDevice::getInputDevices + virtual const BlackMisc::Audio::CAudioDeviceInfoList &getInputDevices() const override { return m_devices; } + + //! \copydoc IAudioInputDevice::getCurrentInputDevice + virtual const BlackMisc::Audio::CAudioDeviceInfo &getCurrentInputDevice() const override { return m_currentDevice; } + + //! \copydoc IAudioInputDevice::setInputDevice + virtual void setInputDevice(const BlackMisc::Audio::CAudioDeviceInfo &device) override { m_currentDevice = device; } + + private: + BlackMisc::Audio::CAudioDeviceInfoList m_devices; /*!< in and output devices */ + BlackMisc::Audio::CAudioDeviceInfo m_currentDevice; + }; + //! Audio Output Device class IAudioOutputDevice : public QObject { diff --git a/src/blackcore/context/contextaudioimpl.cpp b/src/blackcore/context/contextaudioimpl.cpp index 875b85d06..cde8098da 100644 --- a/src/blackcore/context/contextaudioimpl.cpp +++ b/src/blackcore/context/contextaudioimpl.cpp @@ -54,38 +54,13 @@ namespace BlackCore IContextAudio(mode, runtime), m_voice(new CVoiceVatlib()) { - //! \todo KB 2018-11 those are supposed to be Qt::QueuedConnection, but not yet changed (risk to break something) - m_channel1 = m_voice->createVoiceChannel(); - connect(m_channel1.data(), &IVoiceChannel::connectionStatusChanged, this, &CContextAudio::onConnectionStatusChanged); - connect(m_channel1.data(), &IVoiceChannel::userJoinedRoom, this, &CContextAudio::onUserJoinedRoom); - connect(m_channel1.data(), &IVoiceChannel::userLeftRoom, this, &CContextAudio::onUserLeftRoom); - m_channel2 = m_voice->createVoiceChannel(); - connect(m_channel2.data(), &IVoiceChannel::connectionStatusChanged, this, &CContextAudio::onConnectionStatusChanged); - connect(m_channel2.data(), &IVoiceChannel::userJoinedRoom, this, &CContextAudio::onUserJoinedRoom); - connect(m_channel2.data(), &IVoiceChannel::userLeftRoom, this, &CContextAudio::onUserLeftRoom); + initVoiceChannels(); + initInputDevice(); + initOutputDevice(); + initAudioMixer(); - m_voiceInputDevice = m_voice->createInputDevice(); - m_voiceOutputDevice = m_voice->createOutputDevice(); - - m_audioMixer = m_voice->createAudioMixer(); - - m_voice->connectVoice(m_voiceInputDevice.get(), m_audioMixer.get(), IAudioMixer::InputMicrophone); - m_voice->connectVoice(m_channel1.data(), m_audioMixer.get(), IAudioMixer::InputVoiceChannel1); - m_voice->connectVoice(m_channel2.data(), m_audioMixer.get(), IAudioMixer::InputVoiceChannel2); - m_voice->connectVoice(m_audioMixer.get(), IAudioMixer::OutputOutputDevice1, m_voiceOutputDevice.get()); - m_voice->connectVoice(m_audioMixer.get(), IAudioMixer::OutputVoiceChannel1, m_channel1.data()); - m_voice->connectVoice(m_audioMixer.get(), IAudioMixer::OutputVoiceChannel2, m_channel2.data()); - - m_audioMixer->makeMixerConnection(IAudioMixer::InputVoiceChannel1, IAudioMixer::OutputOutputDevice1); - m_audioMixer->makeMixerConnection(IAudioMixer::InputVoiceChannel2, IAudioMixer::OutputOutputDevice1); this->setVoiceOutputVolume(90); - m_unusedVoiceChannels.push_back(m_channel1); - m_unusedVoiceChannels.push_back(m_channel2); - - m_voiceChannelOutputPortMapping[m_channel1] = IAudioMixer::OutputVoiceChannel1; - m_voiceChannelOutputPortMapping[m_channel2] = IAudioMixer::OutputVoiceChannel2; - m_selcalPlayer = new CSelcalPlayer(QAudioDeviceInfo::defaultOutputDevice(), this); this->changeDeviceSettings(); @@ -105,6 +80,69 @@ namespace BlackCore return this; } + void CContextAudio::initVoiceChannels() + { + //! \todo KB 2018-11 those are supposed to be Qt::QueuedConnection, but not yet changed (risk to break something) + m_channel1 = m_voice->createVoiceChannel(); + connect(m_channel1.data(), &IVoiceChannel::connectionStatusChanged, this, &CContextAudio::onConnectionStatusChanged); + connect(m_channel1.data(), &IVoiceChannel::userJoinedRoom, this, &CContextAudio::onUserJoinedRoom); + connect(m_channel1.data(), &IVoiceChannel::userLeftRoom, this, &CContextAudio::onUserLeftRoom); + m_channel2 = m_voice->createVoiceChannel(); + connect(m_channel2.data(), &IVoiceChannel::connectionStatusChanged, this, &CContextAudio::onConnectionStatusChanged); + connect(m_channel2.data(), &IVoiceChannel::userJoinedRoom, this, &CContextAudio::onUserJoinedRoom); + connect(m_channel2.data(), &IVoiceChannel::userLeftRoom, this, &CContextAudio::onUserLeftRoom); + + m_unusedVoiceChannels.push_back(m_channel1); + m_unusedVoiceChannels.push_back(m_channel2); + + m_voiceChannelOutputPortMapping[m_channel1] = IAudioMixer::OutputVoiceChannel1; + m_voiceChannelOutputPortMapping[m_channel2] = IAudioMixer::OutputVoiceChannel2; + } + + void CContextAudio::initInputDevice() + { + #ifdef Q_OS_MAC + CMacOSMicrophoneAccess::AuthorizationStatus status = m_micAccess.getAuthorizationStatus(); + if (status == CMacOSMicrophoneAccess::Authorized) + { + m_voiceInputDevice = m_voice->createInputDevice(); + } + else if (status == CMacOSMicrophoneAccess::NotDetermined) + { + m_voiceInputDevice.reset(new CAudioInputDeviceDummy(this)); + connect(&m_micAccess, &CMacOSMicrophoneAccess::permissionRequestAnswered, this, &CContextAudio::delayedInitMicrophone); + m_micAccess.requestAccess(); + } + else + { + m_voiceInputDevice.reset(new CAudioInputDeviceDummy(this)); + CLogMessage(this).error(u"Microphone access not granted. Voice input will not work."); + } + #else + m_voiceInputDevice = m_voice->createInputDevice(); + #endif + } + + void CContextAudio::initOutputDevice() + { + m_voiceOutputDevice = m_voice->createOutputDevice(); + } + + void CContextAudio::initAudioMixer() + { + m_audioMixer = m_voice->createAudioMixer(); + + m_voice->connectVoice(m_voiceInputDevice.get(), m_audioMixer.get(), IAudioMixer::InputMicrophone); + m_voice->connectVoice(m_channel1.data(), m_audioMixer.get(), IAudioMixer::InputVoiceChannel1); + m_voice->connectVoice(m_channel2.data(), m_audioMixer.get(), IAudioMixer::InputVoiceChannel2); + m_voice->connectVoice(m_audioMixer.get(), IAudioMixer::OutputOutputDevice1, m_voiceOutputDevice.get()); + m_voice->connectVoice(m_audioMixer.get(), IAudioMixer::OutputVoiceChannel1, m_channel1.data()); + m_voice->connectVoice(m_audioMixer.get(), IAudioMixer::OutputVoiceChannel2, m_channel2.data()); + + m_audioMixer->makeMixerConnection(IAudioMixer::InputVoiceChannel1, IAudioMixer::OutputOutputDevice1); + m_audioMixer->makeMixerConnection(IAudioMixer::InputVoiceChannel2, IAudioMixer::OutputOutputDevice1); + } + CContextAudio::~CContextAudio() { this->leaveAllVoiceRooms(); @@ -643,5 +681,14 @@ namespace BlackCore return voiceChannel; } + + + #ifdef Q_OS_MAC + void CContextAudio::delayedInitMicrophone() + { + m_voiceInputDevice = m_voice->createInputDevice(); + m_voice->connectVoice(m_voiceInputDevice.get(), m_audioMixer.get(), IAudioMixer::InputMicrophone); + } + #endif } // namespace } // namespace diff --git a/src/blackcore/context/contextaudioimpl.h b/src/blackcore/context/contextaudioimpl.h index 71aaad82f..8a1b9b3e0 100644 --- a/src/blackcore/context/contextaudioimpl.h +++ b/src/blackcore/context/contextaudioimpl.h @@ -30,6 +30,7 @@ #include "blackmisc/network/userlist.h" #include "blackmisc/settingscache.h" #include "blackmisc/icons.h" +#include "blackmisc/macos/microphoneaccess.h" #include "blacksound/selcalplayer.h" #include "blacksound/notificationplayer.h" @@ -120,6 +121,12 @@ namespace BlackCore CContextAudio *registerWithDBus(BlackMisc::CDBusServer *server); private: + void initVoiceChannels(); + void initInputDevice(); + void initOutputDevice(); + void initAudioMixer(); + void initVoiceVatlib(bool allocateInput = true); + //! \copydoc IVoice::connectionStatusChanged //! \sa IContextAudio::changedVoiceRooms void onConnectionStatusChanged(IVoiceChannel::ConnectionStatus oldStatus, IVoiceChannel::ConnectionStatus newStatus); @@ -152,6 +159,11 @@ namespace BlackCore const int MinUnmuteVolume = 20; //!< minimum volume when unmuted int m_outVolumeBeforeMute = 90; + #ifdef Q_OS_MAC + BlackMisc::CMacOSMicrophoneAccess m_micAccess; + void delayedInitMicrophone(); + #endif + // For easy access. QSharedPointer m_channel1; QSharedPointer m_channel2; diff --git a/src/blackmisc/blackmisc.pro b/src/blackmisc/blackmisc.pro index b2267df81..14b69c689 100644 --- a/src/blackmisc/blackmisc.pro +++ b/src/blackmisc/blackmisc.pro @@ -70,6 +70,10 @@ SOURCES += *.cpp \ $$PWD/test/*.cpp \ $$PWD/weather/*.cpp +macx { + HEADERS += $$PWD/macos/microphoneaccess.h + OBJECTIVE_SOURCES += $$PWD/macos/microphoneaccess.mm +} INCLUDEPATH *= $$EXTERNALSROOT/common/include/crashpad INCLUDEPATH *= $$EXTERNALSROOT/common/include/crashpad/mini_chromium @@ -87,7 +91,7 @@ msvc { CONFIG(debug, debug|release): LIBS *= -lclientd -lutild -lbased -lRpcrt4 -lAdvapi32 CONFIG(release, debug|release): LIBS *= -lclient -lutil -lbase -lRpcrt4 -lAdvapi32 } -macx: LIBS += -lclient -lutil -lbase -lbsm -framework Security -framework CoreFoundation -framework ApplicationServices -framework Foundation +macx: LIBS += -lclient -lutil -lbase -lbsm -framework AVFoundation -framework Security -framework CoreFoundation -framework ApplicationServices -framework Foundation unix:!macx: LIBS *= -lclient -lutil -lbase DESTDIR = $$DestRoot/lib diff --git a/src/blackmisc/macos/microphoneaccess.h b/src/blackmisc/macos/microphoneaccess.h new file mode 100644 index 000000000..c29fa0939 --- /dev/null +++ b/src/blackmisc/macos/microphoneaccess.h @@ -0,0 +1,48 @@ +/* Copyright (C) 2019 + * swift project Community / Contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +//! \file + +#ifndef BLACKMISC_AUDIOACCESSREQUEST_H +#define BLACKMISC_AUDIOACCESSREQUEST_H + +#include "blackmisc/blackmiscexport.h" +#include +#include + +namespace BlackMisc +{ + //! Wrapper around MacOS 10.14 AVCaptureDevice AVCaptureDevice authorization + class BLACKMISC_EXPORT CMacOSMicrophoneAccess : public QObject + { + Q_OBJECT + public: + //! Authorization status + enum AuthorizationStatus + { + Authorized, + Denied, + NotDetermined + }; + + //! Constructor + CMacOSMicrophoneAccess(QObject *parent = nullptr); + + //! Request access + void requestAccess(); + + //! Get current authorization status + AuthorizationStatus getAuthorizationStatus(); + + signals: + //! User has answered the permission request popup + void permissionRequestAnswered(bool granted); + }; +} + +#endif diff --git a/src/blackmisc/macos/microphoneaccess.mm b/src/blackmisc/macos/microphoneaccess.mm new file mode 100644 index 000000000..b4b5586b4 --- /dev/null +++ b/src/blackmisc/macos/microphoneaccess.mm @@ -0,0 +1,62 @@ +/* Copyright (C) 2019 + * swift project Community / Contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +#include "microphoneaccess.h" +#import + +namespace BlackMisc +{ +//#ifdef Q_OS_MAC + + BlackMisc::CMacOSMicrophoneAccess::CMacOSMicrophoneAccess(QObject *parent) : + QObject(parent) + { } + + void CMacOSMicrophoneAccess::requestAccess() + { + if (@available(macOS 10.14, *)) + { + NSString *mediaType = AVMediaTypeAudio; + [AVCaptureDevice requestAccessForMediaType:mediaType completionHandler:^(BOOL granted) + { + emit permissionRequestAnswered(granted); + }]; + } + else + { + emit permissionRequestAnswered(true); + } + + } + + CMacOSMicrophoneAccess::AuthorizationStatus CMacOSMicrophoneAccess::getAuthorizationStatus() + { + if (@available(macOS 10.14, *)) + { + NSString *mediaType = AVMediaTypeAudio; + AVAuthorizationStatus authStatus = [AVCaptureDevice authorizationStatusForMediaType:mediaType]; + if(authStatus == AVAuthorizationStatusAuthorized) + { + return AuthorizationStatus::Authorized; + } + else if(authStatus == AVAuthorizationStatusNotDetermined) + { + return AuthorizationStatus::NotDetermined; + } + return AuthorizationStatus::Denied; + } + else + { + return AuthorizationStatus::Authorized; + } + + } + +// #endif +} + diff --git a/src/swiftcore/Info.plist b/src/swiftcore/Info.plist new file mode 100644 index 000000000..c4c48045e --- /dev/null +++ b/src/swiftcore/Info.plist @@ -0,0 +1,30 @@ + + + + + CFBundleName + swift core + CFBundleDisplayName + swift core + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleGetInfoString + swift project - free and open source pilot client + CFBundleIconFile + ${ASSETCATALOG_COMPILER_APPICON_NAME} + CFBundleIdentifier + ${PRODUCT_BUNDLE_IDENTIFIER} + CFBundlePackageType + APPL + CFBundleSignature + ${QMAKE_PKGINFO_TYPEINFO} + LSMinimumSystemVersion + ${MACOSX_DEPLOYMENT_TARGET} + NSPrincipalClass + NSApplication + NSSupportsAutomaticGraphicsSwitching + + NSMicrophoneUsageDescription + This application needs access to your Microphone for Vatsim Voice. + + diff --git a/src/swiftcore/swiftcore.pro b/src/swiftcore/swiftcore.pro index 3811bbc18..437e5dae1 100644 --- a/src/swiftcore/swiftcore.pro +++ b/src/swiftcore/swiftcore.pro @@ -28,6 +28,9 @@ target.path = $$PREFIX/bin INSTALLS += target macx { + QMAKE_TARGET_BUNDLE_PREFIX = "org.swift-project" + QMAKE_INFO_PLIST = Info.plist + # Modifies plugin path qtconf.path = $$PREFIX/bin/swiftcore.app/Contents/Resources qtconf.files = qt.conf diff --git a/src/swiftdata/Info.plist b/src/swiftdata/Info.plist new file mode 100644 index 000000000..ec28bef06 --- /dev/null +++ b/src/swiftdata/Info.plist @@ -0,0 +1,28 @@ + + + + + CFBundleName + swift data + CFBundleDisplayName + swift data + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleGetInfoString + swift project - free and open source pilot client + CFBundleIconFile + ${ASSETCATALOG_COMPILER_APPICON_NAME} + CFBundleIdentifier + ${PRODUCT_BUNDLE_IDENTIFIER} + CFBundlePackageType + APPL + CFBundleSignature + ${QMAKE_PKGINFO_TYPEINFO} + LSMinimumSystemVersion + ${MACOSX_DEPLOYMENT_TARGET} + NSPrincipalClass + NSApplication + NSSupportsAutomaticGraphicsSwitching + + + diff --git a/src/swiftdata/swiftdata.pro b/src/swiftdata/swiftdata.pro index 7ad073234..d9ae482ab 100644 --- a/src/swiftdata/swiftdata.pro +++ b/src/swiftdata/swiftdata.pro @@ -29,6 +29,9 @@ target.path = $$PREFIX/bin INSTALLS += target macx { + QMAKE_TARGET_BUNDLE_PREFIX = "org.swift-project" + QMAKE_INFO_PLIST = Info.plist + # Modifies plugin path qtconf.path = $$PREFIX/bin/swiftdata.app/Contents/Resources qtconf.files = qt.conf diff --git a/src/swiftguistandard/Info.plist b/src/swiftguistandard/Info.plist new file mode 100644 index 000000000..276c58b37 --- /dev/null +++ b/src/swiftguistandard/Info.plist @@ -0,0 +1,30 @@ + + + + + CFBundleName + swift gui + CFBundleDisplayName + swift gui + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleGetInfoString + swift project - free and open source pilot client + CFBundleIconFile + ${ASSETCATALOG_COMPILER_APPICON_NAME} + CFBundleIdentifier + ${PRODUCT_BUNDLE_IDENTIFIER} + CFBundlePackageType + APPL + CFBundleSignature + ${QMAKE_PKGINFO_TYPEINFO} + LSMinimumSystemVersion + ${MACOSX_DEPLOYMENT_TARGET} + NSPrincipalClass + NSApplication + NSSupportsAutomaticGraphicsSwitching + + NSMicrophoneUsageDescription + This application needs access to your Microphone for Vatsim Voice. + + diff --git a/src/swiftguistandard/swiftguistandard.pro b/src/swiftguistandard/swiftguistandard.pro index aa6e3c065..5271b843a 100644 --- a/src/swiftguistandard/swiftguistandard.pro +++ b/src/swiftguistandard/swiftguistandard.pro @@ -29,6 +29,9 @@ target.path = $$PREFIX/bin INSTALLS += target macx { + QMAKE_TARGET_BUNDLE_PREFIX = "org.swift-project" + QMAKE_INFO_PLIST = Info.plist + # Modifies plugin path qtconf.path = $$PREFIX/bin/swiftguistd.app/Contents/Resources qtconf.files = qt.conf diff --git a/src/swiftlauncher/Info.plist b/src/swiftlauncher/Info.plist new file mode 100644 index 000000000..bad414ecf --- /dev/null +++ b/src/swiftlauncher/Info.plist @@ -0,0 +1,28 @@ + + + + + CFBundleName + swift launcher + CFBundleDisplayName + swift launcher + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleGetInfoString + swift project - free and open source pilot client + CFBundleIconFile + ${ASSETCATALOG_COMPILER_APPICON_NAME} + CFBundleIdentifier + ${PRODUCT_BUNDLE_IDENTIFIER} + CFBundlePackageType + APPL + CFBundleSignature + ${QMAKE_PKGINFO_TYPEINFO} + LSMinimumSystemVersion + ${MACOSX_DEPLOYMENT_TARGET} + NSPrincipalClass + NSApplication + NSSupportsAutomaticGraphicsSwitching + + + diff --git a/src/swiftlauncher/swiftlauncher.pro b/src/swiftlauncher/swiftlauncher.pro index bb5155813..a658421fd 100644 --- a/src/swiftlauncher/swiftlauncher.pro +++ b/src/swiftlauncher/swiftlauncher.pro @@ -30,6 +30,9 @@ target.path = $$PREFIX/bin INSTALLS += target macx { + QMAKE_TARGET_BUNDLE_PREFIX = "org.swift-project" + QMAKE_INFO_PLIST = Info.plist + # Modifies plugin path qtconf.path = $$PREFIX/bin/swiftlauncher.app/Contents/Resources qtconf.files = qt.conf