diff options
Diffstat (limited to 'src/corelib/platform/darwin')
9 files changed, 810 insertions, 0 deletions
diff --git a/src/corelib/platform/darwin/qdarwinpermissionplugin.mm b/src/corelib/platform/darwin/qdarwinpermissionplugin.mm new file mode 100644 index 0000000000..5c527f396c --- /dev/null +++ b/src/corelib/platform/darwin/qdarwinpermissionplugin.mm @@ -0,0 +1,90 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qdarwinpermissionplugin_p.h" + +QT_BEGIN_NAMESPACE + +QDarwinPermissionPlugin::QDarwinPermissionPlugin(QDarwinPermissionHandler *handler) + : QPermissionPlugin() + , m_handler(handler) +{ +} + +QDarwinPermissionPlugin::~QDarwinPermissionPlugin() +{ + [m_handler release]; +} + +Qt::PermissionStatus QDarwinPermissionPlugin::checkPermission(const QPermission &permission) +{ + return [m_handler checkPermission:permission]; +} + +void QDarwinPermissionPlugin::requestPermission(const QPermission &permission, const PermissionCallback &callback) +{ + if (!verifyUsageDescriptions(permission)) { + callback(Qt::PermissionStatus::Denied); + return; + } + + [m_handler requestPermission:permission withCallback:[=](Qt::PermissionStatus status) { + // In case the callback comes in on a secondary thread we need to marshal it + // back to the main thread. And if it doesn't, we still want to propagate it + // via an event, to avoid any GCD locks deadlocking the application on iOS + // if the user responds to the result by running a nested event loop. + // Luckily Qt::QueuedConnection gives us exactly what we need. + QMetaObject::invokeMethod(this, "permissionUpdated", Qt::QueuedConnection, + Q_ARG(Qt::PermissionStatus, status), Q_ARG(PermissionCallback, callback)); + }]; +} + +void QDarwinPermissionPlugin::permissionUpdated(Qt::PermissionStatus status, const PermissionCallback &callback) +{ + callback(status); +} + +bool QDarwinPermissionPlugin::verifyUsageDescriptions(const QPermission &permission) +{ + // FIXME: Look up the responsible process and inspect that, + // as that's what needs to have the usage descriptions. + // FIXME: Verify entitlements if the process is sandboxed. + auto *infoDictionary = NSBundle.mainBundle.infoDictionary; + for (auto description : [m_handler usageDescriptionsFor:permission]) { + if (!infoDictionary[description.toNSString()]) { + qCWarning(lcPermissions) << + "Requesting" << permission.type().name() << + "requires" << description << "in Info.plist"; + return false; + } + } + return true; +} + +QT_END_NAMESPACE + +QT_USE_NAMESPACE + +@implementation QDarwinPermissionHandler + +- (Qt::PermissionStatus)checkPermission:(QPermission)permission +{ + Q_UNREACHABLE(); // All handlers should at least provide a check +} + +- (void)requestPermission:(QPermission)permission withCallback:(PermissionCallback)callback +{ + Q_UNUSED(permission); + qCWarning(lcPermissions).nospace() << "Could not request " << permission.type().name() << ". " + << "Please make sure you have included the required usage description in your Info.plist"; + callback(Qt::PermissionStatus::Denied); +} + +- (QStringList)usageDescriptionsFor:(QPermission)permission +{ + return {}; +} + +@end + +#include "moc_qdarwinpermissionplugin_p.cpp" diff --git a/src/corelib/platform/darwin/qdarwinpermissionplugin_bluetooth.mm b/src/corelib/platform/darwin/qdarwinpermissionplugin_bluetooth.mm new file mode 100644 index 0000000000..0cd375561f --- /dev/null +++ b/src/corelib/platform/darwin/qdarwinpermissionplugin_bluetooth.mm @@ -0,0 +1,86 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qdarwinpermissionplugin_p_p.h" + +#include <deque> + +#include <CoreBluetooth/CoreBluetooth.h> + +@interface QDarwinBluetoothPermissionHandler () <CBCentralManagerDelegate> +@property (nonatomic, retain) CBCentralManager *manager; +@end + +@implementation QDarwinBluetoothPermissionHandler { + std::deque<PermissionCallback> m_callbacks; +} + +- (instancetype)init +{ + if ((self = [super init])) + self.manager = nil; + + return self; +} + +- (Qt::PermissionStatus)checkPermission:(QPermission)permission +{ + Q_UNUSED(permission); + return [self currentStatus]; +} + +- (Qt::PermissionStatus)currentStatus +{ + auto status = CBCentralManager.authorization; + switch (status) { + case CBManagerAuthorizationNotDetermined: + return Qt::PermissionStatus::Undetermined; + case CBManagerAuthorizationRestricted: + case CBManagerAuthorizationDenied: + return Qt::PermissionStatus::Denied; + case CBManagerAuthorizationAllowedAlways: + return Qt::PermissionStatus::Granted; + } + + qCWarning(lcPermissions) << "Unknown permission status" << status << "detected in" << self; + return Qt::PermissionStatus::Denied; +} + +- (void)requestPermission:(QPermission)permission withCallback:(PermissionCallback)callback +{ + m_callbacks.push_back(callback); + if (!self.manager) { + self.manager = [[[CBCentralManager alloc] + initWithDelegate:self queue:dispatch_get_main_queue()] autorelease]; + } +} + +- (void)centralManagerDidUpdateState:(CBCentralManager *)manager +{ + Q_ASSERT(manager == self.manager); + Q_ASSERT(!m_callbacks.empty()); + + auto status = [self currentStatus]; + + for (auto callback : m_callbacks) + callback(status); + + m_callbacks = {}; + self.manager = nil; +} + +- (QStringList)usageDescriptionsFor:(QPermission)permission +{ + Q_UNUSED(permission); +#ifdef Q_OS_MACOS + if (QOperatingSystemVersion::current() > QOperatingSystemVersion::MacOSBigSur) +#endif + { + return { "NSBluetoothAlwaysUsageDescription" }; + } + + return {}; +} +@end + +#include "moc_qdarwinpermissionplugin_p_p.cpp" diff --git a/src/corelib/platform/darwin/qdarwinpermissionplugin_calendar.mm b/src/corelib/platform/darwin/qdarwinpermissionplugin_calendar.mm new file mode 100644 index 0000000000..a3eddd6d8f --- /dev/null +++ b/src/corelib/platform/darwin/qdarwinpermissionplugin_calendar.mm @@ -0,0 +1,71 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qdarwinpermissionplugin_p_p.h" + +#include <EventKit/EventKit.h> + +@interface QDarwinCalendarPermissionHandler () +@property (nonatomic, retain) EKEventStore *eventStore; +@end + +@implementation QDarwinCalendarPermissionHandler +- (Qt::PermissionStatus)checkPermission:(QPermission)permission +{ + Q_UNUSED(permission); + return [self currentStatus]; +} + +- (Qt::PermissionStatus)currentStatus +{ + auto status = [EKEventStore authorizationStatusForEntityType:EKEntityTypeEvent]; + switch (status) { + case EKAuthorizationStatusNotDetermined: + return Qt::PermissionStatus::Undetermined; + case EKAuthorizationStatusRestricted: + case EKAuthorizationStatusDenied: + return Qt::PermissionStatus::Denied; + case EKAuthorizationStatusAuthorized: + return Qt::PermissionStatus::Granted; +#if QT_MACOS_IOS_PLATFORM_SDK_EQUAL_OR_ABOVE(140000, 170000) + case EKAuthorizationStatusWriteOnly: + // FIXME: Add WriteOnly AccessMode + return Qt::PermissionStatus::Denied; +#endif + } + + qCWarning(lcPermissions) << "Unknown permission status" << status << "detected in" << self; + return Qt::PermissionStatus::Denied; +} + +- (QStringList)usageDescriptionsFor:(QPermission)permission +{ + Q_UNUSED(permission); + return { "NSCalendarsUsageDescription" }; +} + +- (void)requestPermission:(QPermission)permission withCallback:(PermissionCallback)callback +{ + if (!self.eventStore) { + // Note: Creating the EKEventStore results in warnings in the + // console about "An error occurred in the persistent store". + // This seems like a EventKit API bug. + self.eventStore = [[EKEventStore new] autorelease]; + } + + [self.eventStore requestAccessToEntityType:EKEntityTypeEvent + completion:^(BOOL granted, NSError * _Nullable error) { + Q_UNUSED(granted); // We use status instead + // Permission denied will result in an error, which we don't + // want to report/log, so we ignore the error and just report + // the status. + Q_UNUSED(error); + + callback([self currentStatus]); + } + ]; +} + +@end + +#include "moc_qdarwinpermissionplugin_p_p.cpp" diff --git a/src/corelib/platform/darwin/qdarwinpermissionplugin_camera.mm b/src/corelib/platform/darwin/qdarwinpermissionplugin_camera.mm new file mode 100644 index 0000000000..51c517d6f3 --- /dev/null +++ b/src/corelib/platform/darwin/qdarwinpermissionplugin_camera.mm @@ -0,0 +1,42 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qdarwinpermissionplugin_p_p.h" + +#include <AVFoundation/AVFoundation.h> + +QT_DEFINE_PERMISSION_STATUS_CONVERTER(AVAuthorizationStatus); + +#ifndef BUILDING_PERMISSION_REQUEST + +@implementation QDarwinCameraPermissionHandler +- (Qt::PermissionStatus)checkPermission:(QPermission)permission +{ + const auto status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo]; + return nativeStatusToQtStatus(status); +} + +- (QStringList)usageDescriptionsFor:(QPermission)permission +{ + Q_UNUSED(permission); + return { "NSCameraUsageDescription" }; +} +@end + +#include "moc_qdarwinpermissionplugin_p_p.cpp" + +#else // Building request + +@implementation QDarwinCameraPermissionHandler (Request) +- (void)requestPermission:(QPermission)permission withCallback:(PermissionCallback)callback +{ + [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) + { + Q_UNUSED(granted); // We use status instead + const auto status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo]; + callback(nativeStatusToQtStatus(status)); + }]; +} +@end + +#endif // BUILDING_PERMISSION_REQUEST diff --git a/src/corelib/platform/darwin/qdarwinpermissionplugin_contacts.mm b/src/corelib/platform/darwin/qdarwinpermissionplugin_contacts.mm new file mode 100644 index 0000000000..3221b6dc1d --- /dev/null +++ b/src/corelib/platform/darwin/qdarwinpermissionplugin_contacts.mm @@ -0,0 +1,58 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qdarwinpermissionplugin_p_p.h" + +#include <Contacts/Contacts.h> + +QT_DEFINE_PERMISSION_STATUS_CONVERTER(CNAuthorizationStatus); + +@interface QDarwinContactsPermissionHandler () +@property (nonatomic, retain) CNContactStore *contactStore; +@end + +@implementation QDarwinContactsPermissionHandler +- (Qt::PermissionStatus)checkPermission:(QPermission)permission +{ + Q_UNUSED(permission); + return [self currentStatus]; +} + +- (Qt::PermissionStatus)currentStatus +{ + const auto status = [CNContactStore authorizationStatusForEntityType:CNEntityTypeContacts]; + return nativeStatusToQtStatus(status); +} + +- (QStringList)usageDescriptionsFor:(QPermission)permission +{ + Q_UNUSED(permission); + return { "NSContactsUsageDescription" }; +} + +- (void)requestPermission:(QPermission)permission withCallback:(PermissionCallback)callback +{ + if (!self.contactStore) { + // Note: Creating the CNContactStore results in warnings in the + // console about "Attempted to register account monitor for types + // client is not authorized to access", mentioning CardDAV, LDAP, + // and Exchange. This seems like a Contacts API bug. + self.contactStore = [[CNContactStore new] autorelease]; + } + + [self.contactStore requestAccessForEntityType:CNEntityTypeContacts + completionHandler:^(BOOL granted, NSError * _Nullable error) { + Q_UNUSED(granted); // We use status instead + // Permission denied will result in an error, which we don't + // want to report/log, so we ignore the error and just report + // the status. + Q_UNUSED(error); + + callback([self currentStatus]); + } + ]; +} + +@end + +#include "moc_qdarwinpermissionplugin_p_p.cpp" diff --git a/src/corelib/platform/darwin/qdarwinpermissionplugin_location.mm b/src/corelib/platform/darwin/qdarwinpermissionplugin_location.mm new file mode 100644 index 0000000000..4aa35f3920 --- /dev/null +++ b/src/corelib/platform/darwin/qdarwinpermissionplugin_location.mm @@ -0,0 +1,259 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qdarwinpermissionplugin_p_p.h" + +#include <deque> + +#include <CoreLocation/CoreLocation.h> + +@interface QDarwinLocationPermissionHandler () <CLLocationManagerDelegate> +@property (nonatomic, retain) CLLocationManager *manager; +@end + +Q_LOGGING_CATEGORY(lcLocationPermission, "qt.permissions.location"); + +void warmUpLocationServices() +{ + // After creating a CLLocationManager the authorizationStatus + // will initially be kCLAuthorizationStatusNotDetermined. The + // status will then update to an actual status if the app was + // previously authorized/denied once the location services + // do some initial book-keeping in the background. By kicking + // off a CLLocationManager early on here, we ensure that by + // the time the user calls checkPermission the authorization + // status has been resolved. + qCDebug(lcLocationPermission) << "Warming up location services"; + [[CLLocationManager new] release]; +} + +Q_CONSTRUCTOR_FUNCTION(warmUpLocationServices); + +struct PermissionRequest +{ + QPermission permission; + PermissionCallback callback; +}; + +@implementation QDarwinLocationPermissionHandler { + std::deque<PermissionRequest> m_requests; +} + +- (instancetype)init +{ + if ((self = [super init])) { + // The delegate callbacks will come in on the thread that + // the CLLocationManager is created on, and we want those + // to come in on the main thread, so we defer creation + // of the manger until requestPermission, where we know + // we are on the main thread. + self.manager = nil; + } + + return self; +} + +- (Qt::PermissionStatus)checkPermission:(QPermission)permission +{ + const auto locationPermission = *permission.value<QLocationPermission>(); + + auto status = [self authorizationStatus:locationPermission]; + if (status != Qt::PermissionStatus::Granted) + return status; + + return [self accuracyAuthorization:locationPermission]; +} + +- (Qt::PermissionStatus)authorizationStatus:(QLocationPermission)permission +{ + NSString *bundleIdentifier = NSBundle.mainBundle.bundleIdentifier; + if (!bundleIdentifier || !bundleIdentifier.length) { + qCWarning(lcLocationPermission) << "Missing bundle identifier" + << "in Info.plist. Can not use location permissions."; + return Qt::PermissionStatus::Denied; + } + +#if defined(Q_OS_VISIONOS) + if (permission.availability() == QLocationPermission::Always) + return Qt::PermissionStatus::Denied; +#endif + + auto status = [self authorizationStatus]; + switch (status) { + case kCLAuthorizationStatusRestricted: + case kCLAuthorizationStatusDenied: + return Qt::PermissionStatus::Denied; + case kCLAuthorizationStatusNotDetermined: + return Qt::PermissionStatus::Undetermined; +#if !defined(Q_OS_VISIONOS) + case kCLAuthorizationStatusAuthorizedAlways: + return Qt::PermissionStatus::Granted; +#endif +#if defined(Q_OS_IOS) || defined(Q_OS_VISIONOS) + case kCLAuthorizationStatusAuthorizedWhenInUse: + if (permission.availability() == QLocationPermission::Always) + return Qt::PermissionStatus::Denied; + return Qt::PermissionStatus::Granted; +#endif + } + + qCWarning(lcPermissions) << "Unknown permission status" << status << "detected in" << self; + return Qt::PermissionStatus::Denied; +} + +- (CLAuthorizationStatus)authorizationStatus +{ + if (self.manager) + return self.manager.authorizationStatus; + + return QT_IGNORE_DEPRECATIONS(CLLocationManager.authorizationStatus); +} + +- (Qt::PermissionStatus)accuracyAuthorization:(QLocationPermission)permission +{ + auto status = self.manager.accuracyAuthorization; + + switch (status) { + case CLAccuracyAuthorizationFullAccuracy: + return Qt::PermissionStatus::Granted; + case CLAccuracyAuthorizationReducedAccuracy: + if (permission.accuracy() == QLocationPermission::Approximate) + return Qt::PermissionStatus::Granted; + else + return Qt::PermissionStatus::Denied; + } + + qCWarning(lcPermissions) << "Unknown accuracy status" << status << "detected in" << self; + return Qt::PermissionStatus::Denied; +} + +- (QStringList)usageDescriptionsFor:(QPermission)permission +{ +#if defined(Q_OS_MACOS) + return { "NSLocationUsageDescription" }; +#else // iOS 11 and above + QStringList usageDescriptions = { "NSLocationWhenInUseUsageDescription" }; + const auto locationPermission = *permission.value<QLocationPermission>(); + if (locationPermission.availability() == QLocationPermission::Always) + usageDescriptions << "NSLocationAlwaysAndWhenInUseUsageDescription"; + return usageDescriptions; +#endif +} + +- (void)requestPermission:(QPermission)permission withCallback:(PermissionCallback)callback +{ + const bool requestAlreadyInFlight = !m_requests.empty(); + + m_requests.push_back({ permission, callback }); + + if (requestAlreadyInFlight) { + qCDebug(lcLocationPermission).nospace() << "Already processing " + << m_requests.front().permission << ". Deferring request"; + } else { + [self requestQueuedPermission]; + } +} + +- (void)requestQueuedPermission +{ + Q_ASSERT(!m_requests.empty()); + const auto permission = m_requests.front().permission; + + qCDebug(lcLocationPermission) << "Requesting" << permission; + + if (!self.manager) { + self.manager = [[CLLocationManager new] autorelease]; + self.manager.delegate = self; + } + + const auto locationPermission = *permission.value<QLocationPermission>(); + switch (locationPermission.availability()) { + case QLocationPermission::WhenInUse: + // The documentation specifies that requestWhenInUseAuthorization can + // only be called when the current authorization status is undetermined. + switch ([self authorizationStatus]) { + case kCLAuthorizationStatusNotDetermined: + [self.manager requestWhenInUseAuthorization]; + break; + default: + [self deliverResult]; + } + break; + case QLocationPermission::Always: +#if defined(Q_OS_VISIONOS) + [self deliverResult]; // Not supported +#else + // The documentation specifies that requestAlwaysAuthorization can only + // be called when the current authorization status is either undetermined, + // or authorized when in use. + switch ([self authorizationStatus]) { + case kCLAuthorizationStatusNotDetermined: + [self.manager requestAlwaysAuthorization]; + break; +#ifdef Q_OS_IOS + case kCLAuthorizationStatusAuthorizedWhenInUse: + // Unfortunately when asking for AlwaysAuthorization when in + // the WhenInUse state, to "upgrade" the permission, the iOS + // location system will not give us a callback if the user + // denies the request, leaving us hanging without a way to + // respond to the permission request. + qCWarning(lcLocationPermission) << "QLocationPermission::WhenInUse" + << "can not be upgraded to QLocationPermission::Always on iOS." + << "Please request QLocationPermission::Always directly."; + Q_FALLTHROUGH(); +#endif + default: + [self deliverResult]; + } +#endif + break; + } +} + +- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status +{ + qCDebug(lcLocationPermission) << "Processing authorization" + << "update with status" << status; + + if (m_requests.empty()) { + qCDebug(lcLocationPermission) << "No requests in flight. Ignoring."; + return; + } + + if (status == kCLAuthorizationStatusNotDetermined) { + // Initializing a CLLocationManager will result in an initial + // callback to the delegate even before we've requested any + // location permissions. Normally we would ignore this callback + // due to the request queue check above, but if this callback + // comes in after the application has requested a permission + // we don't want to report the undetermined status, but rather + // wait for the actual result to come in. + qCDebug(lcLocationPermission) << "Ignoring delegate callback" + << "with status kCLAuthorizationStatusNotDetermined"; + return; + } + + [self deliverResult]; +} + +- (void)deliverResult +{ + auto request = m_requests.front(); + m_requests.pop_front(); + + auto status = [self checkPermission:request.permission]; + qCDebug(lcLocationPermission) << "Result for" + << request.permission << "was" << status; + + request.callback(status); + + if (!m_requests.empty()) { + qCDebug(lcLocationPermission) << "Still have" + << m_requests.size() << "deferred request(s)"; + [self requestQueuedPermission]; + } +} + +@end + +#include "moc_qdarwinpermissionplugin_p_p.cpp" diff --git a/src/corelib/platform/darwin/qdarwinpermissionplugin_microphone.mm b/src/corelib/platform/darwin/qdarwinpermissionplugin_microphone.mm new file mode 100644 index 0000000000..5dc434309d --- /dev/null +++ b/src/corelib/platform/darwin/qdarwinpermissionplugin_microphone.mm @@ -0,0 +1,42 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qdarwinpermissionplugin_p_p.h" + +#include <AVFoundation/AVFoundation.h> + +QT_DEFINE_PERMISSION_STATUS_CONVERTER(AVAuthorizationStatus); + +#ifndef BUILDING_PERMISSION_REQUEST + +@implementation QDarwinMicrophonePermissionHandler +- (Qt::PermissionStatus)checkPermission:(QPermission)permission +{ + const auto status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio]; + return nativeStatusToQtStatus(status); +} + +- (QStringList)usageDescriptionsFor:(QPermission)permission +{ + Q_UNUSED(permission); + return { "NSMicrophoneUsageDescription" }; +} +@end + +#include "moc_qdarwinpermissionplugin_p_p.cpp" + +#else // Building request + +@implementation QDarwinMicrophonePermissionHandler (Request) +- (void)requestPermission:(QPermission)permission withCallback:(PermissionCallback)callback +{ + [AVCaptureDevice requestAccessForMediaType:AVMediaTypeAudio completionHandler:^(BOOL granted) + { + Q_UNUSED(granted); // We use status instead + const auto status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio]; + callback(nativeStatusToQtStatus(status)); + }]; +} +@end + +#endif // BUILDING_PERMISSION_REQUEST diff --git a/src/corelib/platform/darwin/qdarwinpermissionplugin_p.h b/src/corelib/platform/darwin/qdarwinpermissionplugin_p.h new file mode 100644 index 0000000000..03530133ad --- /dev/null +++ b/src/corelib/platform/darwin/qdarwinpermissionplugin_p.h @@ -0,0 +1,58 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QDARWINPERMISSIONPLUGIN_P_H +#define QDARWINPERMISSIONPLUGIN_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. This header file may change +// from version to version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/qnamespace.h> +#include <QtCore/private/qpermissions_p.h> +#include <QtCore/private/qcore_mac_p.h> + +#if defined(__OBJC__) +#include <Foundation/NSObject.h> +#endif + +QT_USE_NAMESPACE + +using namespace QPermissions::Private; + +#if defined(__OBJC__) +Q_CORE_EXPORT +#endif +QT_DECLARE_NAMESPACED_OBJC_INTERFACE(QDarwinPermissionHandler, NSObject +- (Qt::PermissionStatus)checkPermission:(QPermission)permission; +- (void)requestPermission:(QPermission)permission withCallback:(PermissionCallback)callback; +- (QStringList)usageDescriptionsFor:(QPermission)permission; +) + +QT_BEGIN_NAMESPACE + +class Q_CORE_EXPORT QDarwinPermissionPlugin : public QPermissionPlugin +{ + Q_OBJECT +public: + QDarwinPermissionPlugin(QDarwinPermissionHandler *handler); + ~QDarwinPermissionPlugin(); + + Qt::PermissionStatus checkPermission(const QPermission &permission) override; + void requestPermission(const QPermission &permission, const PermissionCallback &callback) override; + +private: + Q_SLOT void permissionUpdated(Qt::PermissionStatus status, const PermissionCallback &callback); + bool verifyUsageDescriptions(const QPermission &permission); + QDarwinPermissionHandler *m_handler = nullptr; +}; + +QT_END_NAMESPACE + +#endif // QDARWINPERMISSIONPLUGIN_P_H diff --git a/src/corelib/platform/darwin/qdarwinpermissionplugin_p_p.h b/src/corelib/platform/darwin/qdarwinpermissionplugin_p_p.h new file mode 100644 index 0000000000..649af06507 --- /dev/null +++ b/src/corelib/platform/darwin/qdarwinpermissionplugin_p_p.h @@ -0,0 +1,104 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QDARWINPERMISSIONPLUGIN_P_P_H +#define QDARWINPERMISSIONPLUGIN_P_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. This header file may change +// from version to version without notice, or even be removed. +// +// We mean it. +// + +#if !defined(QT_DARWIN_PERMISSION_PLUGIN) +#error "This header should only be included from permission plugins" +#endif + +#include <QtCore/qnamespace.h> +#include <QtCore/private/qpermissions_p.h> +#include <QtCore/private/qcore_mac_p.h> + +#include "qdarwinpermissionplugin_p.h" + +using namespace QPermissions::Private; + +#ifndef QT_JOIN +#define QT_JOIN_IMPL(A, B) A ## B +#define QT_JOIN(A, B) QT_JOIN_IMPL(A, B) +#endif + +#define PERMISSION_PLUGIN_NAME(SUFFIX) \ + QT_JOIN(QT_JOIN(QT_JOIN( \ + QDarwin, QT_DARWIN_PERMISSION_PLUGIN), Permission), SUFFIX) + +#define PERMISSION_PLUGIN_CLASSNAME PERMISSION_PLUGIN_NAME(Plugin) +#define PERMISSION_PLUGIN_HANDLER PERMISSION_PLUGIN_NAME(Handler) + +QT_DECLARE_NAMESPACED_OBJC_INTERFACE( + PERMISSION_PLUGIN_HANDLER, + QDarwinPermissionHandler +) + +QT_BEGIN_NAMESPACE + +class Q_CORE_EXPORT PERMISSION_PLUGIN_CLASSNAME : public QDarwinPermissionPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA( + IID QPermissionPluginInterface_iid + FILE "QDarwin" QT_STRINGIFY(QT_DARWIN_PERMISSION_PLUGIN) "PermissionPlugin.json") +public: + PERMISSION_PLUGIN_CLASSNAME() + : QDarwinPermissionPlugin([[PERMISSION_PLUGIN_HANDLER alloc] init]) + {} +}; + +QT_END_NAMESPACE + +// Request +#if defined(BUILDING_PERMISSION_REQUEST) +extern "C" void PERMISSION_PLUGIN_NAME(Request)() {} +#endif + +// ------------------------------------------------------- + +namespace { +template <typename NativeStatus> +struct NativeStatusHelper; + +template <typename NativeStatus> +Qt::PermissionStatus nativeStatusToQtStatus(NativeStatus status) +{ + using Converter = NativeStatusHelper<NativeStatus>; + switch (status) { + case Converter::Authorized: + return Qt::PermissionStatus::Granted; + case Converter::Denied: + case Converter::Restricted: + return Qt::PermissionStatus::Denied; + case Converter::Undetermined: + return Qt::PermissionStatus::Undetermined; + } + qCWarning(lcPermissions) << "Unknown permission status" << status << "detected in" + << QT_STRINGIFY(QT_DARWIN_PERMISSION_PLUGIN); + return Qt::PermissionStatus::Denied; +} +} // namespace + +#define QT_DEFINE_PERMISSION_STATUS_CONVERTER(NativeStatus) \ +namespace { template<> \ +struct NativeStatusHelper<NativeStatus> \ +{\ + enum { \ + Authorized = NativeStatus##Authorized, \ + Denied = NativeStatus##Denied, \ + Restricted = NativeStatus##Restricted, \ + Undetermined = NativeStatus##NotDetermined \ + }; \ +}; } + +#endif // QDARWINPERMISSIONPLUGIN_P_P_H |