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