1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Copyright (C) 2014 Petroules Corporation.
5** Contact: https://www.qt.io/licensing/
6**
7** This file is part of the QtCore module of the Qt Toolkit.
8**
9** $QT_BEGIN_LICENSE:LGPL$
10** Commercial License Usage
11** Licensees holding valid commercial Qt licenses may use this file in
12** accordance with the commercial license agreement provided with the
13** Software or, alternatively, in accordance with the terms contained in
14** a written agreement between you and The Qt Company. For licensing terms
15** and conditions see https://www.qt.io/terms-conditions. For further
16** information use the contact form at https://www.qt.io/contact-us.
17**
18** GNU Lesser General Public License Usage
19** Alternatively, this file may be used under the terms of the GNU Lesser
20** General Public License version 3 as published by the Free Software
21** Foundation and appearing in the file LICENSE.LGPL3 included in the
22** packaging of this file. Please review the following information to
23** ensure the GNU Lesser General Public License version 3 requirements
24** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
25**
26** GNU General Public License Usage
27** Alternatively, this file may be used under the terms of the GNU
28** General Public License version 2.0 or (at your option) the GNU General
29** Public license version 3 or any later version approved by the KDE Free
30** Qt Foundation. The licenses are as published by the Free Software
31** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
32** included in the packaging of this file. Please review the following
33** information to ensure the GNU General Public License requirements will
34** be met: https://www.gnu.org/licenses/gpl-2.0.html and
35** https://www.gnu.org/licenses/gpl-3.0.html.
36**
37** $QT_END_LICENSE$
38**
39****************************************************************************/
40
41#include <private/qcore_mac_p.h>
42
43#ifdef Q_OS_MACOS
44#include <AppKit/AppKit.h>
45#endif
46
47#if defined(QT_PLATFORM_UIKIT)
48#include <UIKit/UIKit.h>
49#endif
50
51#include <new>
52#include <execinfo.h>
53#include <dlfcn.h>
54#include <cxxabi.h>
55#include <objc/runtime.h>
56#include <mach-o/dyld.h>
57
58#include <qdebug.h>
59
60#include "qhash.h"
61#include "qpair.h"
62#include "qmutex.h"
63#include "qvarlengtharray.h"
64#include "private/qlocking_p.h"
65
66QT_BEGIN_NAMESPACE
67
68// --------------------------------------------------------------------------
69
70QCFString::operator QString() const
71{
72    if (string.isEmpty() && value)
73        const_cast<QCFString*>(this)->string = QString::fromCFString(value);
74    return string;
75}
76
77QCFString::operator CFStringRef() const
78{
79    if (!value)
80        const_cast<QCFString*>(this)->value = string.toCFString();
81    return value;
82}
83
84// --------------------------------------------------------------------------
85
86#if defined(QT_USE_APPLE_UNIFIED_LOGGING)
87
88bool AppleUnifiedLogger::willMirrorToStderr()
89{
90    // When running under Xcode or LLDB, one or more of these variables will
91    // be set, which triggers libsystem_trace.dyld to log messages to stderr
92    // as well, via_os_log_impl_mirror_to_stderr. Un-setting these variables
93    // is not an option, as that would silence normal NSLog or os_log calls,
94    // so instead we skip our own stderr output. See rdar://36919139.
95    static bool willMirror = qEnvironmentVariableIsSet("OS_ACTIVITY_DT_MODE")
96                                 || qEnvironmentVariableIsSet("ACTIVITY_LOG_STDERR")
97                                 || qEnvironmentVariableIsSet("CFLOG_FORCE_STDERR");
98    return willMirror;
99}
100
101QT_MAC_WEAK_IMPORT(_os_log_default);
102bool AppleUnifiedLogger::messageHandler(QtMsgType msgType, const QMessageLogContext &context,
103                                        const QString &message, const QString &optionalSubsystem)
104{
105    QString subsystem = optionalSubsystem;
106    if (subsystem.isNull()) {
107        static QString bundleIdentifier = []() {
108            if (CFBundleRef bundle = CFBundleGetMainBundle()) {
109                if (CFStringRef identifier = CFBundleGetIdentifier(bundle))
110                    return QString::fromCFString(identifier);
111            }
112            return QString();
113        }();
114        subsystem = bundleIdentifier;
115    }
116
117    const bool isDefault = !context.category || !strcmp(context.category, "default");
118    os_log_t log = isDefault ? OS_LOG_DEFAULT :
119        cachedLog(subsystem, QString::fromLatin1(context.category));
120    os_log_type_t logType = logTypeForMessageType(msgType);
121
122    if (!os_log_type_enabled(log, logType))
123        return false;
124
125    // Logging best practices says we should not include symbolication
126    // information or source file line numbers in messages, as the system
127    // will automatically captures this information. In our case, what
128    // the system captures is the call to os_log_with_type below, which
129    // isn't really useful, but we still don't want to include the context's
130    // info, as that would clutter the logging output. See rdar://35958308.
131
132    // The format must be a string constant, so we can't pass on the
133    // message. This means we won't be able to take advantage of the
134    // unified logging's custom format specifiers such as %{BOOL}d.
135    // We use the 'public' format specifier to prevent the logging
136    // system from redacting our log message.
137    os_log_with_type(log, logType, "%{public}s", qPrintable(message));
138
139    return willMirrorToStderr();
140}
141
142os_log_type_t AppleUnifiedLogger::logTypeForMessageType(QtMsgType msgType)
143{
144    switch (msgType) {
145    case QtDebugMsg: return OS_LOG_TYPE_DEBUG;
146    case QtInfoMsg: return OS_LOG_TYPE_INFO;
147    case QtWarningMsg: return OS_LOG_TYPE_DEFAULT;
148    case QtCriticalMsg: return OS_LOG_TYPE_ERROR;
149    case QtFatalMsg: return OS_LOG_TYPE_FAULT;
150    }
151
152    return OS_LOG_TYPE_DEFAULT;
153}
154
155os_log_t AppleUnifiedLogger::cachedLog(const QString &subsystem, const QString &category)
156{
157    static QBasicMutex mutex;
158    const auto locker = qt_scoped_lock(mutex);
159
160    static QHash<QPair<QString, QString>, os_log_t> logs;
161    const auto cacheKey = qMakePair(subsystem, category);
162    os_log_t log = logs.value(cacheKey);
163
164    if (!log) {
165        log = os_log_create(subsystem.toLatin1().constData(),
166            category.toLatin1().constData());
167        logs.insert(cacheKey, log);
168
169        // Technically we should release the os_log_t resource when done
170        // with it, but since we don't know when a category is disabled
171        // we keep all cached os_log_t instances until shutdown, where
172        // the OS will clean them up for us.
173    }
174
175    return log;
176}
177
178#endif // QT_USE_APPLE_UNIFIED_LOGGING
179
180// -------------------------------------------------------------------------
181
182QDebug operator<<(QDebug dbg, const NSObject *nsObject)
183{
184    return dbg << (nsObject ?
185            dbg.verbosity() > 2 ?
186                nsObject.debugDescription.UTF8String :
187                nsObject.description.UTF8String
188        : "NSObject(0x0)");
189}
190
191QDebug operator<<(QDebug dbg, CFStringRef stringRef)
192{
193    if (!stringRef)
194        return dbg << "CFStringRef(0x0)";
195
196    if (const UniChar *chars = CFStringGetCharactersPtr(stringRef))
197        dbg << QString::fromRawData(reinterpret_cast<const QChar *>(chars), CFStringGetLength(stringRef));
198    else
199        dbg << QString::fromCFString(stringRef);
200
201    return dbg;
202}
203
204// Prevents breaking the ODR in case we introduce support for more types
205// later on, and lets the user override our default QDebug operators.
206#define QT_DECLARE_WEAK_QDEBUG_OPERATOR_FOR_CF_TYPE(CFType) \
207    __attribute__((weak)) Q_DECLARE_QDEBUG_OPERATOR_FOR_CF_TYPE(CFType)
208
209QT_FOR_EACH_CORE_FOUNDATION_TYPE(QT_DECLARE_WEAK_QDEBUG_OPERATOR_FOR_CF_TYPE);
210QT_FOR_EACH_MUTABLE_CORE_FOUNDATION_TYPE(QT_DECLARE_WEAK_QDEBUG_OPERATOR_FOR_CF_TYPE);
211QT_FOR_EACH_CORE_GRAPHICS_TYPE(QT_DECLARE_WEAK_QDEBUG_OPERATOR_FOR_CF_TYPE);
212QT_FOR_EACH_MUTABLE_CORE_GRAPHICS_TYPE(QT_DECLARE_WEAK_QDEBUG_OPERATOR_FOR_CF_TYPE);
213
214// -------------------------------------------------------------------------
215
216QT_END_NAMESPACE
217QT_USE_NAMESPACE
218@interface QT_MANGLE_NAMESPACE(QMacAutoReleasePoolTracker) : NSObject
219@end
220
221@implementation QT_MANGLE_NAMESPACE(QMacAutoReleasePoolTracker) {
222    NSAutoreleasePool **m_pool;
223}
224
225- (instancetype)initWithPool:(NSAutoreleasePool **)pool
226{
227    if ((self = [self init]))
228        m_pool = pool;
229    return self;
230}
231
232- (void)dealloc
233{
234    if (*m_pool) {
235        // The pool is still valid, which means we're not being drained from
236        // the corresponding QMacAutoReleasePool (see below).
237
238        // QMacAutoReleasePool has only a single member, the NSAutoreleasePool*
239        // so the address of that member is also the QMacAutoReleasePool itself.
240        QMacAutoReleasePool *pool = reinterpret_cast<QMacAutoReleasePool *>(m_pool);
241        qWarning() << "Premature drain of" << pool << "This can happen if you've allocated"
242            << "the pool on the heap, or as a member of a heap-allocated object. This is not a"
243            << "supported use of QMacAutoReleasePool, and might result in crashes when objects"
244            << "in the pool are deallocated and then used later on under the assumption they"
245            << "will be valid until" << pool << "has been drained.";
246
247        // Reset the pool so that it's not drained again later on
248        *m_pool = nullptr;
249    }
250
251    [super dealloc];
252}
253@end
254QT_NAMESPACE_ALIAS_OBJC_CLASS(QMacAutoReleasePoolTracker);
255
256QT_BEGIN_NAMESPACE
257
258QMacAutoReleasePool::QMacAutoReleasePool()
259    : pool([[NSAutoreleasePool alloc] init])
260{
261    Class trackerClass = [QMacAutoReleasePoolTracker class];
262
263#ifdef QT_DEBUG
264    void *poolFrame = nullptr;
265    if (__builtin_available(macOS 10.14, iOS 12.0, tvOS 12.0, watchOS 5.0, *)) {
266        void *frame;
267        if (backtrace_from_fp(__builtin_frame_address(0), &frame, 1))
268            poolFrame = frame;
269    } else {
270        static const int maxFrames = 3;
271        void *callstack[maxFrames];
272        if (backtrace(callstack, maxFrames) == maxFrames)
273            poolFrame = callstack[maxFrames - 1];
274    }
275
276    if (poolFrame) {
277        Dl_info info;
278        if (dladdr(poolFrame, &info) && info.dli_sname) {
279            const char *symbolName = info.dli_sname;
280            if (symbolName[0] == '_') {
281                int status;
282                if (char *demangled = abi::__cxa_demangle(info.dli_sname, nullptr, 0, &status))
283                    symbolName = demangled;
284            }
285
286            char *className = nullptr;
287            asprintf(&className, "  ^-- allocated in function: %s", symbolName);
288
289            if (Class existingClass = objc_getClass(className))
290                trackerClass = existingClass;
291            else
292                trackerClass = objc_duplicateClass(trackerClass, className, 0);
293
294            free(className);
295
296            if (symbolName != info.dli_sname)
297                free((char*)symbolName);
298        }
299    }
300#endif
301
302    [[[trackerClass alloc] initWithPool:
303        reinterpret_cast<NSAutoreleasePool **>(&pool)] autorelease];
304}
305
306QMacAutoReleasePool::~QMacAutoReleasePool()
307{
308    if (!pool) {
309        qWarning() << "Prematurely drained pool" << this << "finally drained. Any objects belonging"
310            << "to this pool have already been released, and have potentially been invalid since the"
311            << "premature drain earlier on.";
312        return;
313    }
314
315    // Save and reset pool before draining, so that the pool tracker can know
316    // that it's being drained by its owning pool.
317    NSAutoreleasePool *savedPool = static_cast<NSAutoreleasePool*>(pool);
318    pool = nullptr;
319
320    // Drain behaves the same as release, with the advantage that
321    // if we're ever used in a garbage-collected environment, the
322    // drain acts as a hint to the garbage collector to collect.
323    [savedPool drain];
324}
325
326#ifndef QT_NO_DEBUG_STREAM
327QDebug operator<<(QDebug debug, const QMacAutoReleasePool *pool)
328{
329    QDebugStateSaver saver(debug);
330    debug.nospace();
331    debug << "QMacAutoReleasePool(" << (const void *)pool << ')';
332    return debug;
333}
334
335QDebug operator<<(QDebug debug, const QCFString &string)
336{
337    debug << static_cast<QString>(string);
338    return debug;
339}
340#endif // !QT_NO_DEBUG_STREAM
341
342#ifdef Q_OS_MACOS
343bool qt_mac_applicationIsInDarkMode()
344{
345#if QT_MACOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_14)
346    if (__builtin_available(macOS 10.14, *)) {
347        auto appearance = [NSApp.effectiveAppearance bestMatchFromAppearancesWithNames:
348                @[ NSAppearanceNameAqua, NSAppearanceNameDarkAqua ]];
349        return [appearance isEqualToString:NSAppearanceNameDarkAqua];
350    }
351#endif
352    return false;
353}
354#endif
355
356bool qt_apple_isApplicationExtension()
357{
358    static bool isExtension = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSExtension"];
359    return isExtension;
360}
361
362#if !defined(QT_BOOTSTRAPPED) && !defined(Q_OS_WATCHOS)
363AppleApplication *qt_apple_sharedApplication()
364{
365    // Application extensions are not allowed to access the shared application
366    if (qt_apple_isApplicationExtension()) {
367        qWarning() << "accessing the shared" << [AppleApplication class]
368            << "is not allowed in application extensions";
369
370        // In practice the application is actually available, but the App
371        // review process will likely catch uses of it, so we return nil just
372        // in case, unless we don't care about being App Store compliant.
373#if QT_CONFIG(appstore_compliant)
374        return nil;
375#endif
376    }
377
378    // We use performSelector so that building with -fapplication-extension will
379    // not mistakenly think we're using the shared application in extensions.
380    return [[AppleApplication class] performSelector:@selector(sharedApplication)];
381}
382#endif
383
384#if defined(Q_OS_MACOS) && !defined(QT_BOOTSTRAPPED)
385bool qt_apple_isSandboxed()
386{
387    static bool isSandboxed = []() {
388        QCFType<SecStaticCodeRef> staticCode = nullptr;
389        NSURL *bundleUrl = [[NSBundle mainBundle] bundleURL];
390        if (SecStaticCodeCreateWithPath((__bridge CFURLRef)bundleUrl,
391            kSecCSDefaultFlags, &staticCode) != errSecSuccess)
392            return false;
393
394        QCFType<SecRequirementRef> sandboxRequirement;
395        if (SecRequirementCreateWithString(CFSTR("entitlement[\"com.apple.security.app-sandbox\"] exists"),
396            kSecCSDefaultFlags, &sandboxRequirement) != errSecSuccess)
397            return false;
398
399        if (SecStaticCodeCheckValidityWithErrors(staticCode,
400            kSecCSBasicValidateOnly, sandboxRequirement, nullptr) != errSecSuccess)
401            return false;
402
403        return true;
404    }();
405    return isSandboxed;
406}
407
408QT_END_NAMESPACE
409@implementation NSObject (QtSandboxHelpers)
410- (id)qt_valueForPrivateKey:(NSString *)key
411{
412    if (qt_apple_isSandboxed())
413        return nil;
414
415    return [self valueForKey:key];
416}
417@end
418QT_BEGIN_NAMESPACE
419#endif
420
421#ifdef Q_OS_MACOS
422/*
423    Ensure that Objective-C objects auto-released in main(), directly or indirectly,
424    after QCoreApplication construction, are released when the app goes out of scope.
425    The memory will be reclaimed by the system either way when the process exits,
426    but by having a root level pool we ensure that the objects get their dealloc
427    methods called, which is useful for debugging object ownership graphs, etc.
428*/
429
430QT_END_NAMESPACE
431#define ROOT_LEVEL_POOL_MARKER QT_ROOT_LEVEL_POOL__THESE_OBJECTS_WILL_BE_RELEASED_WHEN_QAPP_GOES_OUT_OF_SCOPE
432@interface QT_MANGLE_NAMESPACE(ROOT_LEVEL_POOL_MARKER) : NSObject @end
433@implementation QT_MANGLE_NAMESPACE(ROOT_LEVEL_POOL_MARKER) @end
434QT_NAMESPACE_ALIAS_OBJC_CLASS(ROOT_LEVEL_POOL_MARKER);
435QT_BEGIN_NAMESPACE
436
437const char ROOT_LEVEL_POOL_DISABLE_SWITCH[] = "QT_DISABLE_ROOT_LEVEL_AUTORELEASE_POOL";
438
439QMacRootLevelAutoReleasePool::QMacRootLevelAutoReleasePool()
440{
441    if (qEnvironmentVariableIsSet(ROOT_LEVEL_POOL_DISABLE_SWITCH))
442        return;
443
444    pool.reset(new QMacAutoReleasePool);
445
446    [[[ROOT_LEVEL_POOL_MARKER alloc] init] autorelease];
447
448    if (qstrcmp(qgetenv("OBJC_DEBUG_MISSING_POOLS"), "YES") == 0) {
449        qDebug("QCoreApplication root level NSAutoreleasePool in place. Break on ~%s and use\n" \
450            "'p [NSAutoreleasePool showPools]' to show leaked objects, or set %s",
451            __FUNCTION__, ROOT_LEVEL_POOL_DISABLE_SWITCH);
452    }
453}
454
455QMacRootLevelAutoReleasePool::~QMacRootLevelAutoReleasePool()
456{
457}
458#endif
459
460// -------------------------------------------------------------------------
461
462#ifdef Q_OS_MACOS
463
464// Use this method to keep all the information in the TextSegment. As long as it is ordered
465// we are in OK shape, and we can influence that ourselves.
466struct KeyPair
467{
468    QChar cocoaKey;
469    Qt::Key qtKey;
470};
471
472bool operator==(const KeyPair &entry, QChar qchar)
473{
474    return entry.cocoaKey == qchar;
475}
476
477bool operator<(const KeyPair &entry, QChar qchar)
478{
479    return entry.cocoaKey < qchar;
480}
481
482bool operator<(QChar qchar, const KeyPair &entry)
483{
484    return qchar < entry.cocoaKey;
485}
486
487bool operator<(const Qt::Key &key, const KeyPair &entry)
488{
489    return key < entry.qtKey;
490}
491
492bool operator<(const KeyPair &entry, const Qt::Key &key)
493{
494    return entry.qtKey < key;
495}
496
497struct qtKey2CocoaKeySortLessThan
498{
499    typedef bool result_type;
500    Q_DECL_CONSTEXPR result_type operator()(const KeyPair &entry1, const KeyPair &entry2) const noexcept
501    {
502        return entry1.qtKey < entry2.qtKey;
503    }
504};
505
506static const int NSEscapeCharacter = 27; // not defined by Cocoa headers
507static const int NumEntries = 59;
508static const KeyPair entries[NumEntries] = {
509    { NSEnterCharacter, Qt::Key_Enter },
510    { NSBackspaceCharacter, Qt::Key_Backspace },
511    { NSTabCharacter, Qt::Key_Tab },
512    { NSNewlineCharacter, Qt::Key_Return },
513    { NSCarriageReturnCharacter, Qt::Key_Return },
514    { NSBackTabCharacter, Qt::Key_Backtab },
515    { NSEscapeCharacter, Qt::Key_Escape },
516    // Cocoa sends us delete when pressing backspace!
517    // (NB when we reverse this list in qtKey2CocoaKey, there
518    // will be two indices of Qt::Key_Backspace. But is seems to work
519    // ok for menu shortcuts (which uses that function):
520    { NSDeleteCharacter, Qt::Key_Backspace },
521    { NSUpArrowFunctionKey, Qt::Key_Up },
522    { NSDownArrowFunctionKey, Qt::Key_Down },
523    { NSLeftArrowFunctionKey, Qt::Key_Left },
524    { NSRightArrowFunctionKey, Qt::Key_Right },
525    { NSF1FunctionKey, Qt::Key_F1 },
526    { NSF2FunctionKey, Qt::Key_F2 },
527    { NSF3FunctionKey, Qt::Key_F3 },
528    { NSF4FunctionKey, Qt::Key_F4 },
529    { NSF5FunctionKey, Qt::Key_F5 },
530    { NSF6FunctionKey, Qt::Key_F6 },
531    { NSF7FunctionKey, Qt::Key_F7 },
532    { NSF8FunctionKey, Qt::Key_F8 },
533    { NSF9FunctionKey, Qt::Key_F9 },
534    { NSF10FunctionKey, Qt::Key_F10 },
535    { NSF11FunctionKey, Qt::Key_F11 },
536    { NSF12FunctionKey, Qt::Key_F12 },
537    { NSF13FunctionKey, Qt::Key_F13 },
538    { NSF14FunctionKey, Qt::Key_F14 },
539    { NSF15FunctionKey, Qt::Key_F15 },
540    { NSF16FunctionKey, Qt::Key_F16 },
541    { NSF17FunctionKey, Qt::Key_F17 },
542    { NSF18FunctionKey, Qt::Key_F18 },
543    { NSF19FunctionKey, Qt::Key_F19 },
544    { NSF20FunctionKey, Qt::Key_F20 },
545    { NSF21FunctionKey, Qt::Key_F21 },
546    { NSF22FunctionKey, Qt::Key_F22 },
547    { NSF23FunctionKey, Qt::Key_F23 },
548    { NSF24FunctionKey, Qt::Key_F24 },
549    { NSF25FunctionKey, Qt::Key_F25 },
550    { NSF26FunctionKey, Qt::Key_F26 },
551    { NSF27FunctionKey, Qt::Key_F27 },
552    { NSF28FunctionKey, Qt::Key_F28 },
553    { NSF29FunctionKey, Qt::Key_F29 },
554    { NSF30FunctionKey, Qt::Key_F30 },
555    { NSF31FunctionKey, Qt::Key_F31 },
556    { NSF32FunctionKey, Qt::Key_F32 },
557    { NSF33FunctionKey, Qt::Key_F33 },
558    { NSF34FunctionKey, Qt::Key_F34 },
559    { NSF35FunctionKey, Qt::Key_F35 },
560    { NSInsertFunctionKey, Qt::Key_Insert },
561    { NSDeleteFunctionKey, Qt::Key_Delete },
562    { NSHomeFunctionKey, Qt::Key_Home },
563    { NSEndFunctionKey, Qt::Key_End },
564    { NSPageUpFunctionKey, Qt::Key_PageUp },
565    { NSPageDownFunctionKey, Qt::Key_PageDown },
566    { NSPrintScreenFunctionKey, Qt::Key_Print },
567    { NSScrollLockFunctionKey, Qt::Key_ScrollLock },
568    { NSPauseFunctionKey, Qt::Key_Pause },
569    { NSSysReqFunctionKey, Qt::Key_SysReq },
570    { NSMenuFunctionKey, Qt::Key_Menu },
571    { NSHelpFunctionKey, Qt::Key_Help },
572};
573static const KeyPair * const end = entries + NumEntries;
574
575QChar qt_mac_qtKey2CocoaKey(Qt::Key key)
576{
577    // The first time this function is called, create a reverse
578    // lookup table sorted on Qt Key rather than Cocoa key:
579    static QVector<KeyPair> rev_entries(NumEntries);
580    static bool mustInit = true;
581    if (mustInit){
582        mustInit = false;
583        for (int i=0; i<NumEntries; ++i)
584            rev_entries[i] = entries[i];
585        std::sort(rev_entries.begin(), rev_entries.end(), qtKey2CocoaKeySortLessThan());
586    }
587    const QVector<KeyPair>::iterator i
588            = std::lower_bound(rev_entries.begin(), rev_entries.end(), key);
589    if ((i == rev_entries.end()) || (key < *i))
590        return QChar();
591    return i->cocoaKey;
592}
593
594Qt::Key qt_mac_cocoaKey2QtKey(QChar keyCode)
595{
596    const KeyPair *i = std::lower_bound(entries, end, keyCode);
597    if ((i == end) || (keyCode < *i))
598        return Qt::Key(keyCode.toUpper().unicode());
599    return i->qtKey;
600}
601
602#endif // Q_OS_MACOS
603
604void qt_apple_check_os_version()
605{
606#if defined(__WATCH_OS_VERSION_MIN_REQUIRED)
607    const char *os = "watchOS";
608    const int version = __WATCH_OS_VERSION_MIN_REQUIRED;
609#elif defined(__TV_OS_VERSION_MIN_REQUIRED)
610    const char *os = "tvOS";
611    const int version = __TV_OS_VERSION_MIN_REQUIRED;
612#elif defined(__IPHONE_OS_VERSION_MIN_REQUIRED)
613    const char *os = "iOS";
614    const int version = __IPHONE_OS_VERSION_MIN_REQUIRED;
615#elif defined(__MAC_OS_X_VERSION_MIN_REQUIRED)
616    const char *os = "macOS";
617    const int version = __MAC_OS_X_VERSION_MIN_REQUIRED;
618#endif
619    const NSOperatingSystemVersion required = (NSOperatingSystemVersion){
620        version / 10000, version / 100 % 100, version % 100};
621    const NSOperatingSystemVersion current = NSProcessInfo.processInfo.operatingSystemVersion;
622    if (![NSProcessInfo.processInfo isOperatingSystemAtLeastVersion:required]) {
623        NSDictionary *plist = NSBundle.mainBundle.infoDictionary;
624        NSString *applicationName = plist[@"CFBundleDisplayName"];
625        if (!applicationName)
626            applicationName = plist[@"CFBundleName"];
627        if (!applicationName)
628            applicationName = NSProcessInfo.processInfo.processName;
629
630        fprintf(stderr, "Sorry, \"%s\" cannot be run on this version of %s. "
631            "Qt requires %s %ld.%ld.%ld or later, you have %s %ld.%ld.%ld.\n",
632            applicationName.UTF8String, os,
633            os, long(required.majorVersion), long(required.minorVersion), long(required.patchVersion),
634            os, long(current.majorVersion), long(current.minorVersion), long(current.patchVersion));
635
636        exit(1);
637    }
638}
639Q_CONSTRUCTOR_FUNCTION(qt_apple_check_os_version);
640
641// -------------------------------------------------------------------------
642
643void QMacKeyValueObserver::addObserver(NSKeyValueObservingOptions options)
644{
645    [object addObserver:observer forKeyPath:keyPath options:options context:callback.get()];
646}
647
648void QMacKeyValueObserver::removeObserver() {
649    if (object)
650        [object removeObserver:observer forKeyPath:keyPath context:callback.get()];
651    object = nil;
652}
653
654KeyValueObserver *QMacKeyValueObserver::observer = [[KeyValueObserver alloc] init];
655
656QT_END_NAMESPACE
657@implementation QT_MANGLE_NAMESPACE(KeyValueObserver)
658- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
659        change:(NSDictionary<NSKeyValueChangeKey, id> *)change context:(void *)context
660{
661    Q_UNUSED(keyPath);
662    Q_UNUSED(object);
663    Q_UNUSED(change);
664
665    (*reinterpret_cast<QMacKeyValueObserver::Callback*>(context))();
666}
667@end
668QT_BEGIN_NAMESPACE
669
670// -------------------------------------------------------------------------
671
672QOperatingSystemVersion QMacVersion::buildSDK(VersionTarget target)
673{
674    switch (target) {
675    case ApplicationBinary: return applicationVersion().second;
676    case QtLibraries: return libraryVersion().second;
677    }
678    Q_UNREACHABLE();
679}
680
681QOperatingSystemVersion QMacVersion::deploymentTarget(VersionTarget target)
682{
683    switch (target) {
684    case ApplicationBinary: return applicationVersion().first;
685    case QtLibraries: return libraryVersion().first;
686    }
687    Q_UNREACHABLE();
688}
689
690QOperatingSystemVersion QMacVersion::currentRuntime()
691{
692    return QOperatingSystemVersion::current();
693}
694
695// Mach-O platforms
696enum Platform {
697   macOS = 1,
698   iOS = 2,
699   tvOS = 3,
700   watchOS = 4,
701   bridgeOS = 5,
702   macCatalyst = 6,
703   iOSSimulator = 7,
704   tvOSSimulator = 8,
705   watchOSSimulator = 9
706};
707
708QMacVersion::VersionTuple QMacVersion::versionsForImage(const mach_header *machHeader)
709{
710    static auto osForLoadCommand = [](uint32_t cmd) {
711        switch (cmd) {
712        case LC_VERSION_MIN_MACOSX: return QOperatingSystemVersion::MacOS;
713        case LC_VERSION_MIN_IPHONEOS: return QOperatingSystemVersion::IOS;
714        case LC_VERSION_MIN_TVOS: return QOperatingSystemVersion::TvOS;
715        case LC_VERSION_MIN_WATCHOS: return QOperatingSystemVersion::WatchOS;
716        default: return QOperatingSystemVersion::Unknown;
717        }
718    };
719
720    static auto osForPlatform = [](uint32_t platform) {
721        switch (platform) {
722        case Platform::macOS:
723            return QOperatingSystemVersion::MacOS;
724        case Platform::iOS:
725        case Platform::iOSSimulator:
726            return QOperatingSystemVersion::IOS;
727        case Platform::tvOS:
728        case Platform::tvOSSimulator:
729            return QOperatingSystemVersion::TvOS;
730        case Platform::watchOS:
731        case Platform::watchOSSimulator:
732            return QOperatingSystemVersion::WatchOS;
733        default:
734            return QOperatingSystemVersion::Unknown;
735        }
736    };
737
738    static auto makeVersionTuple = [](uint32_t dt, uint32_t sdk, QOperatingSystemVersion::OSType osType) {
739        return qMakePair(
740            QOperatingSystemVersion(osType, dt >> 16 & 0xffff, dt >> 8 & 0xff, dt & 0xff),
741            QOperatingSystemVersion(osType, sdk >> 16 & 0xffff, sdk >> 8 & 0xff, sdk & 0xff)
742        );
743    };
744
745    const bool is64Bit = machHeader->magic == MH_MAGIC_64 || machHeader->magic == MH_CIGAM_64;
746    auto commandCursor = uintptr_t(machHeader) + (is64Bit ? sizeof(mach_header_64) : sizeof(mach_header));
747
748    for (uint32_t i = 0; i < machHeader->ncmds; ++i) {
749        load_command *loadCommand = reinterpret_cast<load_command *>(commandCursor);
750        if (loadCommand->cmd == LC_VERSION_MIN_MACOSX || loadCommand->cmd == LC_VERSION_MIN_IPHONEOS
751            || loadCommand->cmd == LC_VERSION_MIN_TVOS || loadCommand->cmd == LC_VERSION_MIN_WATCHOS) {
752            auto versionCommand = reinterpret_cast<version_min_command *>(loadCommand);
753            return makeVersionTuple(versionCommand->version, versionCommand->sdk, osForLoadCommand(loadCommand->cmd));
754#if QT_DARWIN_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_13, __IPHONE_11_0, __TVOS_11_0, __WATCHOS_4_0)
755        } else if (loadCommand->cmd == LC_BUILD_VERSION) {
756            auto versionCommand = reinterpret_cast<build_version_command *>(loadCommand);
757            return makeVersionTuple(versionCommand->minos, versionCommand->sdk, osForPlatform(versionCommand->platform));
758#endif
759        }
760        commandCursor += loadCommand->cmdsize;
761    }
762    Q_ASSERT_X(false, "QMacVersion", "Could not find any version load command");
763    Q_UNREACHABLE();
764}
765
766QMacVersion::VersionTuple QMacVersion::applicationVersion()
767{
768    static VersionTuple version = []() {
769        const mach_header *executableHeader = nullptr;
770        for (uint32_t i = 0; i < _dyld_image_count(); ++i) {
771            auto header = _dyld_get_image_header(i);
772            if (header->filetype == MH_EXECUTE) {
773                executableHeader = header;
774                break;
775            }
776        }
777        Q_ASSERT_X(executableHeader, "QMacVersion", "Failed to resolve Mach-O header of executable");
778        return versionsForImage(executableHeader);
779    }();
780    return version;
781}
782
783QMacVersion::VersionTuple QMacVersion::libraryVersion()
784{
785    static VersionTuple version = []() {
786        Dl_info qtCoreImage;
787        dladdr((const void *)&QMacVersion::libraryVersion, &qtCoreImage);
788        Q_ASSERT_X(qtCoreImage.dli_fbase, "QMacVersion", "Failed to resolve Mach-O header of QtCore");
789        return versionsForImage(static_cast<mach_header*>(qtCoreImage.dli_fbase));
790    }();
791    return version;
792}
793
794// -------------------------------------------------------------------------
795
796QT_END_NAMESPACE
797
798