| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 The Qt Company Ltd. |
| ** Copyright (C) 2014 Petroules Corporation. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the QtCore module of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU Lesser General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU Lesser |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
| ** packaging of this file. Please review the following information to |
| ** ensure the GNU Lesser General Public License version 3 requirements |
| ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 2.0 or (at your option) the GNU General |
| ** Public license version 3 or any later version approved by the KDE Free |
| ** Qt Foundation. The licenses are as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
| ** https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include <private/qcore_mac_p.h> |
| |
| #ifdef Q_OS_MACOS |
| #include <AppKit/AppKit.h> |
| #endif |
| |
| #if defined(QT_PLATFORM_UIKIT) |
| #include <UIKit/UIKit.h> |
| #endif |
| |
| #include <execinfo.h> |
| #include <dlfcn.h> |
| #include <cxxabi.h> |
| #include <objc/runtime.h> |
| |
| #include <qdebug.h> |
| |
| QT_BEGIN_NAMESPACE |
| |
| // ------------------------------------------------------------------------- |
| |
| QDebug operator<<(QDebug dbg, const NSObject *nsObject) |
| { |
| return dbg << (nsObject ? |
| dbg.verbosity() > 2 ? |
| nsObject.debugDescription.UTF8String : |
| nsObject.description.UTF8String |
| : "NSObject(0x0)"); |
| } |
| |
| QDebug operator<<(QDebug dbg, CFStringRef stringRef) |
| { |
| if (!stringRef) |
| return dbg << "CFStringRef(0x0)"; |
| |
| if (const UniChar *chars = CFStringGetCharactersPtr(stringRef)) |
| dbg << QString::fromRawData(reinterpret_cast<const QChar *>(chars), CFStringGetLength(stringRef)); |
| else |
| dbg << QString::fromCFString(stringRef); |
| |
| return dbg; |
| } |
| |
| // Prevents breaking the ODR in case we introduce support for more types |
| // later on, and lets the user override our default QDebug operators. |
| #define QT_DECLARE_WEAK_QDEBUG_OPERATOR_FOR_CF_TYPE(CFType) \ |
| __attribute__((weak)) Q_DECLARE_QDEBUG_OPERATOR_FOR_CF_TYPE(CFType) |
| |
| QT_FOR_EACH_CORE_FOUNDATION_TYPE(QT_DECLARE_WEAK_QDEBUG_OPERATOR_FOR_CF_TYPE); |
| QT_FOR_EACH_MUTABLE_CORE_FOUNDATION_TYPE(QT_DECLARE_WEAK_QDEBUG_OPERATOR_FOR_CF_TYPE); |
| QT_FOR_EACH_CORE_GRAPHICS_TYPE(QT_DECLARE_WEAK_QDEBUG_OPERATOR_FOR_CF_TYPE); |
| QT_FOR_EACH_MUTABLE_CORE_GRAPHICS_TYPE(QT_DECLARE_WEAK_QDEBUG_OPERATOR_FOR_CF_TYPE); |
| |
| // ------------------------------------------------------------------------- |
| |
| QT_END_NAMESPACE |
| QT_USE_NAMESPACE |
| @interface QT_MANGLE_NAMESPACE(QMacAutoReleasePoolTracker) : NSObject |
| @end |
| |
| @implementation QT_MANGLE_NAMESPACE(QMacAutoReleasePoolTracker) { |
| NSAutoreleasePool **m_pool; |
| } |
| |
| - (instancetype)initWithPool:(NSAutoreleasePool **)pool |
| { |
| if ((self = [self init])) |
| m_pool = pool; |
| return self; |
| } |
| |
| - (void)dealloc |
| { |
| if (*m_pool) { |
| // The pool is still valid, which means we're not being drained from |
| // the corresponding QMacAutoReleasePool (see below). |
| |
| // QMacAutoReleasePool has only a single member, the NSAutoreleasePool* |
| // so the address of that member is also the QMacAutoReleasePool itself. |
| QMacAutoReleasePool *pool = reinterpret_cast<QMacAutoReleasePool *>(m_pool); |
| qWarning() << "Premature drain of" << pool << "This can happen if you've allocated" |
| << "the pool on the heap, or as a member of a heap-allocated object. This is not a" |
| << "supported use of QMacAutoReleasePool, and might result in crashes when objects" |
| << "in the pool are deallocated and then used later on under the assumption they" |
| << "will be valid until" << pool << "has been drained."; |
| |
| // Reset the pool so that it's not drained again later on |
| *m_pool = nullptr; |
| } |
| |
| [super dealloc]; |
| } |
| @end |
| QT_NAMESPACE_ALIAS_OBJC_CLASS(QMacAutoReleasePoolTracker); |
| |
| QT_BEGIN_NAMESPACE |
| |
| QMacAutoReleasePool::QMacAutoReleasePool() |
| : pool([[NSAutoreleasePool alloc] init]) |
| { |
| Class trackerClass = [QMacAutoReleasePoolTracker class]; |
| |
| #ifdef QT_DEBUG |
| void *poolFrame = nullptr; |
| if (__builtin_available(macOS 10.14, iOS 12.0, tvOS 12.0, watchOS 5.0, *)) { |
| void *frame; |
| if (backtrace_from_fp(__builtin_frame_address(0), &frame, 1)) |
| poolFrame = frame; |
| } else { |
| static const int maxFrames = 3; |
| void *callstack[maxFrames]; |
| if (backtrace(callstack, maxFrames) == maxFrames) |
| poolFrame = callstack[maxFrames - 1]; |
| } |
| |
| if (poolFrame) { |
| Dl_info info; |
| if (dladdr(poolFrame, &info) && info.dli_sname) { |
| const char *symbolName = info.dli_sname; |
| if (symbolName[0] == '_') { |
| int status; |
| if (char *demangled = abi::__cxa_demangle(info.dli_sname, nullptr, 0, &status)) |
| symbolName = demangled; |
| } |
| |
| char *className = nullptr; |
| asprintf(&className, " ^-- allocated in function: %s", symbolName); |
| |
| if (Class existingClass = objc_getClass(className)) |
| trackerClass = existingClass; |
| else |
| trackerClass = objc_duplicateClass(trackerClass, className, 0); |
| |
| free(className); |
| |
| if (symbolName != info.dli_sname) |
| free((char*)symbolName); |
| } |
| } |
| #endif |
| |
| [[[trackerClass alloc] initWithPool: |
| reinterpret_cast<NSAutoreleasePool **>(&pool)] autorelease]; |
| } |
| |
| QMacAutoReleasePool::~QMacAutoReleasePool() |
| { |
| if (!pool) { |
| qWarning() << "Prematurely drained pool" << this << "finally drained. Any objects belonging" |
| << "to this pool have already been released, and have potentially been invalid since the" |
| << "premature drain earlier on."; |
| return; |
| } |
| |
| // Save and reset pool before draining, so that the pool tracker can know |
| // that it's being drained by its owning pool. |
| NSAutoreleasePool *savedPool = static_cast<NSAutoreleasePool*>(pool); |
| pool = nullptr; |
| |
| // Drain behaves the same as release, with the advantage that |
| // if we're ever used in a garbage-collected environment, the |
| // drain acts as a hint to the garbage collector to collect. |
| [savedPool drain]; |
| } |
| |
| #ifndef QT_NO_DEBUG_STREAM |
| QDebug operator<<(QDebug debug, const QMacAutoReleasePool *pool) |
| { |
| QDebugStateSaver saver(debug); |
| debug.nospace(); |
| debug << "QMacAutoReleasePool(" << (const void *)pool << ')'; |
| return debug; |
| } |
| |
| QDebug operator<<(QDebug debug, const QCFString &string) |
| { |
| debug << static_cast<QString>(string); |
| return debug; |
| } |
| #endif // !QT_NO_DEBUG_STREAM |
| |
| #ifdef Q_OS_MACOS |
| bool qt_mac_applicationIsInDarkMode() |
| { |
| #if QT_MACOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_14) |
| if (__builtin_available(macOS 10.14, *)) { |
| auto appearance = [NSApp.effectiveAppearance bestMatchFromAppearancesWithNames: |
| @[ NSAppearanceNameAqua, NSAppearanceNameDarkAqua ]]; |
| return [appearance isEqualToString:NSAppearanceNameDarkAqua]; |
| } |
| #endif |
| return false; |
| } |
| #endif |
| |
| bool qt_apple_isApplicationExtension() |
| { |
| static bool isExtension = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSExtension"]; |
| return isExtension; |
| } |
| |
| #if !defined(QT_BOOTSTRAPPED) && !defined(Q_OS_WATCHOS) |
| AppleApplication *qt_apple_sharedApplication() |
| { |
| // Application extensions are not allowed to access the shared application |
| if (qt_apple_isApplicationExtension()) { |
| qWarning() << "accessing the shared" << [AppleApplication class] |
| << "is not allowed in application extensions"; |
| |
| // In practice the application is actually available, but the the App |
| // review process will likely catch uses of it, so we return nil just |
| // in case, unless we don't care about being App Store compliant. |
| #if QT_CONFIG(appstore_compliant) |
| return nil; |
| #endif |
| } |
| |
| // We use performSelector so that building with -fapplication-extension will |
| // not mistakenly think we're using the shared application in extensions. |
| return [[AppleApplication class] performSelector:@selector(sharedApplication)]; |
| } |
| #endif |
| |
| #if defined(Q_OS_MACOS) && !defined(QT_BOOTSTRAPPED) |
| bool qt_apple_isSandboxed() |
| { |
| static bool isSandboxed = []() { |
| QCFType<SecStaticCodeRef> staticCode = nullptr; |
| NSURL *bundleUrl = [[NSBundle mainBundle] bundleURL]; |
| if (SecStaticCodeCreateWithPath((__bridge CFURLRef)bundleUrl, |
| kSecCSDefaultFlags, &staticCode) != errSecSuccess) |
| return false; |
| |
| QCFType<SecRequirementRef> sandboxRequirement; |
| if (SecRequirementCreateWithString(CFSTR("entitlement[\"com.apple.security.app-sandbox\"] exists"), |
| kSecCSDefaultFlags, &sandboxRequirement) != errSecSuccess) |
| return false; |
| |
| if (SecStaticCodeCheckValidityWithErrors(staticCode, |
| kSecCSBasicValidateOnly, sandboxRequirement, nullptr) != errSecSuccess) |
| return false; |
| |
| return true; |
| }(); |
| return isSandboxed; |
| } |
| |
| QT_END_NAMESPACE |
| @implementation NSObject (QtSandboxHelpers) |
| - (id)qt_valueForPrivateKey:(NSString *)key |
| { |
| if (qt_apple_isSandboxed()) |
| return nil; |
| |
| return [self valueForKey:key]; |
| } |
| @end |
| QT_BEGIN_NAMESPACE |
| #endif |
| |
| #ifdef Q_OS_MACOS |
| /* |
| Ensure that Objective-C objects auto-released in main(), directly or indirectly, |
| after QCoreApplication construction, are released when the app goes out of scope. |
| The memory will be reclaimed by the system either way when the process exits, |
| but by having a root level pool we ensure that the objects get their dealloc |
| methods called, which is useful for debugging object ownership graphs, etc. |
| */ |
| |
| QT_END_NAMESPACE |
| #define ROOT_LEVEL_POOL_MARKER QT_ROOT_LEVEL_POOL__THESE_OBJECTS_WILL_BE_RELEASED_WHEN_QAPP_GOES_OUT_OF_SCOPE |
| @interface QT_MANGLE_NAMESPACE(ROOT_LEVEL_POOL_MARKER) : NSObject @end |
| @implementation QT_MANGLE_NAMESPACE(ROOT_LEVEL_POOL_MARKER) @end |
| QT_NAMESPACE_ALIAS_OBJC_CLASS(ROOT_LEVEL_POOL_MARKER); |
| QT_BEGIN_NAMESPACE |
| |
| const char ROOT_LEVEL_POOL_DISABLE_SWITCH[] = "QT_DISABLE_ROOT_LEVEL_AUTORELEASE_POOL"; |
| |
| QMacRootLevelAutoReleasePool::QMacRootLevelAutoReleasePool() |
| { |
| if (qEnvironmentVariableIsSet(ROOT_LEVEL_POOL_DISABLE_SWITCH)) |
| return; |
| |
| pool.reset(new QMacAutoReleasePool); |
| |
| [[[ROOT_LEVEL_POOL_MARKER alloc] init] autorelease]; |
| |
| if (qstrcmp(qgetenv("OBJC_DEBUG_MISSING_POOLS"), "YES") == 0) { |
| qDebug("QCoreApplication root level NSAutoreleasePool in place. Break on ~%s and use\n" \ |
| "'p [NSAutoreleasePool showPools]' to show leaked objects, or set %s", |
| __FUNCTION__, ROOT_LEVEL_POOL_DISABLE_SWITCH); |
| } |
| } |
| |
| QMacRootLevelAutoReleasePool::~QMacRootLevelAutoReleasePool() |
| { |
| } |
| #endif |
| |
| // ------------------------------------------------------------------------- |
| |
| #ifdef Q_OS_OSX |
| |
| // Use this method to keep all the information in the TextSegment. As long as it is ordered |
| // we are in OK shape, and we can influence that ourselves. |
| struct KeyPair |
| { |
| QChar cocoaKey; |
| Qt::Key qtKey; |
| }; |
| |
| bool operator==(const KeyPair &entry, QChar qchar) |
| { |
| return entry.cocoaKey == qchar; |
| } |
| |
| bool operator<(const KeyPair &entry, QChar qchar) |
| { |
| return entry.cocoaKey < qchar; |
| } |
| |
| bool operator<(QChar qchar, const KeyPair &entry) |
| { |
| return qchar < entry.cocoaKey; |
| } |
| |
| bool operator<(const Qt::Key &key, const KeyPair &entry) |
| { |
| return key < entry.qtKey; |
| } |
| |
| bool operator<(const KeyPair &entry, const Qt::Key &key) |
| { |
| return entry.qtKey < key; |
| } |
| |
| struct qtKey2CocoaKeySortLessThan |
| { |
| typedef bool result_type; |
| Q_DECL_CONSTEXPR result_type operator()(const KeyPair &entry1, const KeyPair &entry2) const noexcept |
| { |
| return entry1.qtKey < entry2.qtKey; |
| } |
| }; |
| |
| static const int NSEscapeCharacter = 27; // not defined by Cocoa headers |
| static const int NumEntries = 59; |
| static const KeyPair entries[NumEntries] = { |
| { NSEnterCharacter, Qt::Key_Enter }, |
| { NSBackspaceCharacter, Qt::Key_Backspace }, |
| { NSTabCharacter, Qt::Key_Tab }, |
| { NSNewlineCharacter, Qt::Key_Return }, |
| { NSCarriageReturnCharacter, Qt::Key_Return }, |
| { NSBackTabCharacter, Qt::Key_Backtab }, |
| { NSEscapeCharacter, Qt::Key_Escape }, |
| // Cocoa sends us delete when pressing backspace! |
| // (NB when we reverse this list in qtKey2CocoaKey, there |
| // will be two indices of Qt::Key_Backspace. But is seems to work |
| // ok for menu shortcuts (which uses that function): |
| { NSDeleteCharacter, Qt::Key_Backspace }, |
| { NSUpArrowFunctionKey, Qt::Key_Up }, |
| { NSDownArrowFunctionKey, Qt::Key_Down }, |
| { NSLeftArrowFunctionKey, Qt::Key_Left }, |
| { NSRightArrowFunctionKey, Qt::Key_Right }, |
| { NSF1FunctionKey, Qt::Key_F1 }, |
| { NSF2FunctionKey, Qt::Key_F2 }, |
| { NSF3FunctionKey, Qt::Key_F3 }, |
| { NSF4FunctionKey, Qt::Key_F4 }, |
| { NSF5FunctionKey, Qt::Key_F5 }, |
| { NSF6FunctionKey, Qt::Key_F6 }, |
| { NSF7FunctionKey, Qt::Key_F7 }, |
| { NSF8FunctionKey, Qt::Key_F8 }, |
| { NSF9FunctionKey, Qt::Key_F9 }, |
| { NSF10FunctionKey, Qt::Key_F10 }, |
| { NSF11FunctionKey, Qt::Key_F11 }, |
| { NSF12FunctionKey, Qt::Key_F12 }, |
| { NSF13FunctionKey, Qt::Key_F13 }, |
| { NSF14FunctionKey, Qt::Key_F14 }, |
| { NSF15FunctionKey, Qt::Key_F15 }, |
| { NSF16FunctionKey, Qt::Key_F16 }, |
| { NSF17FunctionKey, Qt::Key_F17 }, |
| { NSF18FunctionKey, Qt::Key_F18 }, |
| { NSF19FunctionKey, Qt::Key_F19 }, |
| { NSF20FunctionKey, Qt::Key_F20 }, |
| { NSF21FunctionKey, Qt::Key_F21 }, |
| { NSF22FunctionKey, Qt::Key_F22 }, |
| { NSF23FunctionKey, Qt::Key_F23 }, |
| { NSF24FunctionKey, Qt::Key_F24 }, |
| { NSF25FunctionKey, Qt::Key_F25 }, |
| { NSF26FunctionKey, Qt::Key_F26 }, |
| { NSF27FunctionKey, Qt::Key_F27 }, |
| { NSF28FunctionKey, Qt::Key_F28 }, |
| { NSF29FunctionKey, Qt::Key_F29 }, |
| { NSF30FunctionKey, Qt::Key_F30 }, |
| { NSF31FunctionKey, Qt::Key_F31 }, |
| { NSF32FunctionKey, Qt::Key_F32 }, |
| { NSF33FunctionKey, Qt::Key_F33 }, |
| { NSF34FunctionKey, Qt::Key_F34 }, |
| { NSF35FunctionKey, Qt::Key_F35 }, |
| { NSInsertFunctionKey, Qt::Key_Insert }, |
| { NSDeleteFunctionKey, Qt::Key_Delete }, |
| { NSHomeFunctionKey, Qt::Key_Home }, |
| { NSEndFunctionKey, Qt::Key_End }, |
| { NSPageUpFunctionKey, Qt::Key_PageUp }, |
| { NSPageDownFunctionKey, Qt::Key_PageDown }, |
| { NSPrintScreenFunctionKey, Qt::Key_Print }, |
| { NSScrollLockFunctionKey, Qt::Key_ScrollLock }, |
| { NSPauseFunctionKey, Qt::Key_Pause }, |
| { NSSysReqFunctionKey, Qt::Key_SysReq }, |
| { NSMenuFunctionKey, Qt::Key_Menu }, |
| { NSHelpFunctionKey, Qt::Key_Help }, |
| }; |
| static const KeyPair * const end = entries + NumEntries; |
| |
| QChar qt_mac_qtKey2CocoaKey(Qt::Key key) |
| { |
| // The first time this function is called, create a reverse |
| // lookup table sorted on Qt Key rather than Cocoa key: |
| static QVector<KeyPair> rev_entries(NumEntries); |
| static bool mustInit = true; |
| if (mustInit){ |
| mustInit = false; |
| for (int i=0; i<NumEntries; ++i) |
| rev_entries[i] = entries[i]; |
| std::sort(rev_entries.begin(), rev_entries.end(), qtKey2CocoaKeySortLessThan()); |
| } |
| const QVector<KeyPair>::iterator i |
| = std::lower_bound(rev_entries.begin(), rev_entries.end(), key); |
| if ((i == rev_entries.end()) || (key < *i)) |
| return QChar(); |
| return i->cocoaKey; |
| } |
| |
| Qt::Key qt_mac_cocoaKey2QtKey(QChar keyCode) |
| { |
| const KeyPair *i = std::lower_bound(entries, end, keyCode); |
| if ((i == end) || (keyCode < *i)) |
| return Qt::Key(keyCode.toUpper().unicode()); |
| return i->qtKey; |
| } |
| |
| #endif // Q_OS_OSX |
| |
| void qt_apple_check_os_version() |
| { |
| #if defined(__WATCH_OS_VERSION_MIN_REQUIRED) |
| const char *os = "watchOS"; |
| const int version = __WATCH_OS_VERSION_MIN_REQUIRED; |
| #elif defined(__TV_OS_VERSION_MIN_REQUIRED) |
| const char *os = "tvOS"; |
| const int version = __TV_OS_VERSION_MIN_REQUIRED; |
| #elif defined(__IPHONE_OS_VERSION_MIN_REQUIRED) |
| const char *os = "iOS"; |
| const int version = __IPHONE_OS_VERSION_MIN_REQUIRED; |
| #elif defined(__MAC_OS_X_VERSION_MIN_REQUIRED) |
| const char *os = "macOS"; |
| const int version = __MAC_OS_X_VERSION_MIN_REQUIRED; |
| #endif |
| const NSOperatingSystemVersion required = (NSOperatingSystemVersion){ |
| version / 10000, version / 100 % 100, version % 100}; |
| const NSOperatingSystemVersion current = NSProcessInfo.processInfo.operatingSystemVersion; |
| if (![NSProcessInfo.processInfo isOperatingSystemAtLeastVersion:required]) { |
| NSDictionary *plist = NSBundle.mainBundle.infoDictionary; |
| NSString *applicationName = plist[@"CFBundleDisplayName"]; |
| if (!applicationName) |
| applicationName = plist[@"CFBundleName"]; |
| if (!applicationName) |
| applicationName = NSProcessInfo.processInfo.processName; |
| |
| fprintf(stderr, "Sorry, \"%s\" cannot be run on this version of %s. " |
| "Qt requires %s %ld.%ld.%ld or later, you have %s %ld.%ld.%ld.\n", |
| applicationName.UTF8String, os, |
| os, long(required.majorVersion), long(required.minorVersion), long(required.patchVersion), |
| os, long(current.majorVersion), long(current.minorVersion), long(current.patchVersion)); |
| |
| exit(1); |
| } |
| } |
| Q_CONSTRUCTOR_FUNCTION(qt_apple_check_os_version); |
| |
| // ------------------------------------------------------------------------- |
| |
| void QMacKeyValueObserver::addObserver(NSKeyValueObservingOptions options) |
| { |
| [object addObserver:observer forKeyPath:keyPath options:options context:callback.get()]; |
| } |
| |
| void QMacKeyValueObserver::removeObserver() { |
| if (object) |
| [object removeObserver:observer forKeyPath:keyPath context:callback.get()]; |
| object = nil; |
| } |
| |
| KeyValueObserver *QMacKeyValueObserver::observer = [[KeyValueObserver alloc] init]; |
| |
| QT_END_NAMESPACE |
| @implementation KeyValueObserver |
| - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object |
| change:(NSDictionary<NSKeyValueChangeKey, id> *)change context:(void *)context |
| { |
| Q_UNUSED(keyPath); |
| Q_UNUSED(object); |
| Q_UNUSED(change); |
| |
| (*reinterpret_cast<QMacKeyValueObserver::Callback*>(context))(); |
| } |
| @end |
| QT_BEGIN_NAMESPACE |
| |
| // ------------------------------------------------------------------------- |
| |
| |
| QT_END_NAMESPACE |
| |