Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qcore_mac.mm
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// Copyright (C) 2014 Petroules Corporation.
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
4
5#include <private/qcore_mac_p.h>
6
7#ifdef Q_OS_MACOS
8#include <AppKit/AppKit.h>
9#endif
10
11#if defined(QT_PLATFORM_UIKIT)
12#include <UIKit/UIKit.h>
13#endif
14
15#include <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
16
17#include <new>
18#include <execinfo.h>
19#include <dlfcn.h>
20#include <cxxabi.h>
21#include <objc/runtime.h>
22#include <mach-o/dyld.h>
23#include <sys/sysctl.h>
24#include <spawn.h>
25
26#include <qdebug.h>
27#include <qpoint.h>
28#include <qsize.h>
29#include <qrect.h>
30#include <qmargins.h>
31
32#include "qendian.h"
33#include "qhash.h"
34#include "qmutex.h"
36#include "private/qlocking_p.h"
37
38#if !defined(QT_BOOTSTRAPPED)
39#include <thread>
40#endif
41
42#if !defined(QT_APPLE_NO_PRIVATE_APIS)
43extern "C" {
44typedef uint32_t csr_config_t;
45extern int csr_get_active_config(csr_config_t *) __attribute__((weak_import));
46
47#ifdef QT_BUILD_INTERNAL
48int responsibility_spawnattrs_setdisclaim(posix_spawnattr_t attrs, int disclaim)
49__attribute__((availability(macos,introduced=10.14),weak_import));
50pid_t responsibility_get_pid_responsible_for_pid(pid_t) __attribute__((weak_import));
51char *** _NSGetArgv();
52extern char **environ;
53#endif
54}
55#endif
56
57QT_BEGIN_NAMESPACE
58
59using namespace Qt::StringLiterals;
60
61// --------------------------------------------------------------------------
62
63#if defined(Q_OS_MACOS)
64static void initializeStandardUserDefaults()
65{
66 // The standard user defaults are initialized from an ordered list of domains,
67 // as documented by NSUserDefaults.standardUserDefaults. This includes e.g.
68 // parsing command line arguments, such as -AppleFooBar "baz", as well as
69 // global defaults. To ensure that these defaults are available through
70 // the lower level Core Foundation preferences APIs, we need to initialize
71 // them as early as possible via the Foundation-API, as the lower level APIs
72 // do not do this initialization.
73 Q_UNUSED(NSUserDefaults.standardUserDefaults);
74}
75Q_CONSTRUCTOR_FUNCTION(initializeStandardUserDefaults);
76#endif
77
78// --------------------------------------------------------------------------
79
80QCFString::operator QString() const
81{
82 if (string.isEmpty() && value)
83 const_cast<QCFString*>(this)->string = QString::fromCFString(value);
84 return string;
85}
86
87QCFString::operator CFStringRef() const
88{
89 if (!value)
90 const_cast<QCFString*>(this)->value = string.toCFString();
91 return value;
92}
93
94// --------------------------------------------------------------------------
95
96#if defined(QT_USE_APPLE_UNIFIED_LOGGING)
97
98bool AppleUnifiedLogger::preventsStderrLogging()
99{
100 // os_log will mirror to stderr if OS_ACTIVITY_DT_MODE is set,
101 // regardless of its value. OS_ACTIVITY_MODE then controls whether
102 // to include info and/or debug messages in this mirroring.
103 // For some reason, when launched under lldb (via Xcode or not),
104 // all levels are included.
105
106 // CFLog will normally log to both stderr, and via os_log.
107 // Setting CFLOG_FORCE_DISABLE_STDERR disables the stderr
108 // logging. Setting CFLOG_FORCE_STDERR will both duplicate
109 // CFLog's output to stderr, and trigger OS_ACTIVITY_DT_MODE,
110 // resulting in os_log calls also being mirrored to stderr.
111 // Setting ACTIVITY_LOG_STDERR has the same effect.
112
113 // NSLog is plumbed to CFLog, and will respond to the same
114 // environment variables as CFLog.
115
116 // We want to disable Qt's default stderr log handler when
117 // os_log has already mirrored to stderr.
118 static bool willMirror = qEnvironmentVariableIsSet("OS_ACTIVITY_DT_MODE")
119 || qEnvironmentVariableIsSet("ACTIVITY_LOG_STDERR")
120 || qEnvironmentVariableIsSet("CFLOG_FORCE_STDERR");
121
122 // As well as when we suspect that Xcode is going to present os_log
123 // as structured log messages.
124 static bool disableStderr = qEnvironmentVariableIsSet("CFLOG_FORCE_DISABLE_STDERR");
125
126 return willMirror || disableStderr;
127}
128
129QT_MAC_WEAK_IMPORT(_os_log_default);
130bool AppleUnifiedLogger::messageHandler(QtMsgType msgType, const QMessageLogContext &context,
131 const QString &message, const QString &optionalSubsystem)
132{
133 QString subsystem = optionalSubsystem;
134 if (subsystem.isNull()) {
135 static QString bundleIdentifier = []() {
136 if (CFBundleRef bundle = CFBundleGetMainBundle()) {
137 if (CFStringRef identifier = CFBundleGetIdentifier(bundle))
138 return QString::fromCFString(identifier);
139 }
140 return QString();
141 }();
142 subsystem = bundleIdentifier;
143 }
144
145 const bool isDefault = !context.category || !strcmp(context.category, "default");
146 os_log_t log = isDefault ? OS_LOG_DEFAULT :
147 os_log_create(subsystem.toLatin1().constData(), context.category);
148 os_log_type_t logType = logTypeForMessageType(msgType);
149
150 if (!os_log_type_enabled(log, logType))
151 return false;
152
153 // Logging best practices says we should not include symbolication
154 // information or source file line numbers in messages, as the system
155 // will automatically captures this information. In our case, what
156 // the system captures is the call to os_log_with_type below, which
157 // isn't really useful, but we still don't want to include the context's
158 // info, as that would clutter the logging output. See rdar://35958308.
159
160 // The format must be a string constant, so we can't pass on the
161 // message. This means we won't be able to take advantage of the
162 // unified logging's custom format specifiers such as %{BOOL}d.
163 // We use the 'public' format specifier to prevent the logging
164 // system from redacting our log message.
165 os_log_with_type(log, logType, "%{public}s", qPrintable(message));
166
167 return preventsStderrLogging();
168}
169
170os_log_type_t AppleUnifiedLogger::logTypeForMessageType(QtMsgType msgType)
171{
172 switch (msgType) {
173 case QtDebugMsg: return OS_LOG_TYPE_DEBUG;
174 case QtInfoMsg: return OS_LOG_TYPE_INFO;
175 case QtWarningMsg: return OS_LOG_TYPE_DEFAULT;
176 case QtCriticalMsg: return OS_LOG_TYPE_ERROR;
177 case QtFatalMsg: return OS_LOG_TYPE_FAULT;
178 }
179
180 return OS_LOG_TYPE_DEFAULT;
181}
182
183#endif // QT_USE_APPLE_UNIFIED_LOGGING
184
185#ifndef QT_NO_DEBUG_STREAM
186// -------------------------------------------------------------------------
187
188QDebug operator<<(QDebug dbg, id obj)
189{
190 if (!obj) {
191 // Match NSLog
192 dbg << "(null)";
193 return dbg;
194 }
195
196 for (Class cls = object_getClass(obj); cls; cls = class_getSuperclass(cls)) {
197 if (cls == NSObject.class) {
198 dbg << static_cast<NSObject*>(obj);
199 return dbg;
200 }
201 }
202
203 // Match NSObject.debugDescription
204 const QDebugStateSaver saver(dbg);
205 dbg.nospace() << '<' << object_getClassName(obj) << ": " << static_cast<void*>(obj) << '>';
206 return dbg;
207}
208
209QDebug operator<<(QDebug dbg, const NSObject *nsObject)
210{
211 return dbg << (nsObject ?
212 dbg.verbosity() > 2 ?
213 nsObject.debugDescription.UTF8String :
214 nsObject.description.UTF8String
215 : "NSObject(0x0)");
216}
217
218QDebug operator<<(QDebug dbg, const NSString *nsString)
219{
220 if (!nsString)
221 return dbg << "NSString(0x0)";
222 else
223 return dbg << (__bridge CFStringRef)nsString;
224}
225
226QDebug operator<<(QDebug dbg, CFStringRef stringRef)
227{
228 if (!stringRef)
229 return dbg << "CFStringRef(0x0)";
230
231 if (const UniChar *chars = CFStringGetCharactersPtr(stringRef))
232 dbg << QStringView(reinterpret_cast<const QChar *>(chars), CFStringGetLength(stringRef));
233 else
234 dbg << QString::fromCFString(stringRef);
235
236 return dbg;
237}
238
239QDebug operator<<(QDebug dbg, CGPoint point)
240{
241 dbg << QPointF::fromCGPoint(point);
242 return dbg;
243}
244
245QDebug operator<<(QDebug dbg, CGSize size)
246{
247 dbg << QSizeF::fromCGSize(size);
248 return dbg;
249}
250
251QDebug operator<<(QDebug dbg, CGRect rect)
252{
253 dbg << QRectF::fromCGRect(rect);
254 return dbg;
255}
256
257#if defined(Q_OS_MACOS)
258QDebug operator<<(QDebug dbg, NSEdgeInsets insets)
259#else
260QDebug operator<<(QDebug dbg, UIEdgeInsets insets)
261#endif
262{
263 dbg << QMargins(insets.left, insets.top, insets.right, insets.bottom);
264 return dbg;
265}
266
267// Prevents breaking the ODR in case we introduce support for more types
268// later on, and lets the user override our default QDebug operators.
269#define QT_DECLARE_WEAK_QDEBUG_OPERATOR_FOR_CF_TYPE(CFType)
270 __attribute__((weak)) Q_DECLARE_QDEBUG_OPERATOR_FOR_CF_TYPE(CFType)
271
276#endif // QT_NO_DEBUG_STREAM
277
278// -------------------------------------------------------------------------
279
280QT_END_NAMESPACE
281QT_USE_NAMESPACE
282
283#ifdef QT_DEBUG
284@interface QT_MANGLE_NAMESPACE(QMacAutoReleasePoolTracker) : NSObject
285@end
286
287@implementation QT_MANGLE_NAMESPACE(QMacAutoReleasePoolTracker)
288@end
289QT_NAMESPACE_ALIAS_OBJC_CLASS(QMacAutoReleasePoolTracker);
290#endif // QT_DEBUG
291
292// Use the direct runtime interface to manage autorelease pools, as it
293// has less overhead then allocating NSAutoreleasePools, and allows for
294// a future where we use ARC (where NSAutoreleasePool is not allowed).
295// https://clang.llvm.org/docs/AutomaticReferenceCounting.html#runtime-support
296
297extern "C" {
299void objc_autoreleasePoolPop(void *pool);
300}
301
302QT_BEGIN_NAMESPACE
303
304QMacAutoReleasePool::QMacAutoReleasePool()
305 : pool(objc_autoreleasePoolPush())
306{
307#ifdef QT_DEBUG
308 static const bool debugAutoReleasePools = qEnvironmentVariableIsSet("QT_DARWIN_DEBUG_AUTORELEASEPOOLS");
309 if (!debugAutoReleasePools)
310 return;
311
312 Class trackerClass = [QMacAutoReleasePoolTracker class];
313
314 void *poolFrame = nullptr;
315 void *frames[2];
316 if (backtrace_from_fp(__builtin_frame_address(0), frames, 2))
317 poolFrame = frames[1];
318
319 if (poolFrame) {
320 Dl_info info;
321 if (dladdr(poolFrame, &info) && info.dli_sname) {
322 const char *symbolName = info.dli_sname;
323 if (symbolName[0] == '_') {
324 int status;
325 if (char *demangled = abi::__cxa_demangle(info.dli_sname, nullptr, 0, &status))
326 symbolName = demangled;
327 }
328
329 char *className = nullptr;
330 asprintf(&className, " ^-- allocated in function: %s", symbolName);
331
332 if (Class existingClass = objc_getClass(className))
333 trackerClass = existingClass;
334 else
335 trackerClass = objc_duplicateClass(trackerClass, className, 0);
336
337 free(className);
338
339 if (symbolName != info.dli_sname)
340 free((char*)symbolName);
341 }
342 }
343
344 [[trackerClass new] autorelease];
345#endif // QT_DEBUG
346}
347
348QMacAutoReleasePool::~QMacAutoReleasePool()
349{
350 objc_autoreleasePoolPop(pool);
351}
352
353#ifndef QT_NO_DEBUG_STREAM
354QDebug operator<<(QDebug debug, const QMacAutoReleasePool *pool)
355{
356 QDebugStateSaver saver(debug);
357 debug.nospace();
358 debug << "QMacAutoReleasePool(" << (const void *)pool << ')';
359 return debug;
360}
361
362QDebug operator<<(QDebug debug, const QCFString &string)
363{
364 debug << static_cast<QString>(string);
365 return debug;
366}
367#endif // !QT_NO_DEBUG_STREAM
368
369#if defined(Q_OS_MACOS) && !defined(QT_BOOTSTRAPPED)
370bool qt_mac_runningUnderRosetta()
371{
372 int translated = 0;
373 auto size = sizeof(translated);
374 if (sysctlbyname("sysctl.proc_translated", &translated, &size, nullptr, 0) == 0)
375 return translated;
376 return false;
377}
378
379bool qt_apple_runningWithLiquidGlass()
380{
381 static const bool runningWithLiquidGlass = []{
382 QMacAutoReleasePool pool;
383
384 if (QMacVersion::buildSDK(QMacVersion::ApplicationBinary).majorVersion() < 26)
385 return false;
386
387 if (QMacVersion::currentRuntime().majorVersion() < 26)
388 return false;
389
390 // Word on the street is that the opt out will only work for
391 // macOS 26, but it's not clear whether building against the
392 // Xcode 27 SDK is what will disable it, or simply running on
393 // macOS 27. Let's go with the latter for now.
394 if (QMacVersion::currentRuntime().majorVersion() < 27) {
395 const id liquidGlassOptOut = [NSBundle.mainBundle
396 objectForInfoDictionaryKey:@"UIDesignRequiresCompatibility"];
397 if (liquidGlassOptOut && [liquidGlassOptOut boolValue])
398 return false;
399 }
400
401 return true;
402 }();
403
404 return runningWithLiquidGlass;
405}
406
407std::optional<uint32_t> qt_mac_sipConfiguration()
408{
409 static auto configuration = []() -> std::optional<uint32_t> {
410#if !defined(QT_APPLE_NO_PRIVATE_APIS)
411 csr_config_t config;
412 if (csr_get_active_config && csr_get_active_config(&config) == 0)
413 return config;
414#endif
415
416 QIOType<io_registry_entry_t> nvram = IORegistryEntryFromPath(kIOMainPortDefault, "IODeviceTree:/options");
417 if (!nvram) {
418 qWarning("Failed to locate NVRAM entry in IO registry");
419 return {};
420 }
421
422 QCFType<CFTypeRef> csrConfig = IORegistryEntryCreateCFProperty(nvram,
423 CFSTR("csr-active-config"), kCFAllocatorDefault, IOOptionBits{});
424 if (!csrConfig)
425 return {}; // SIP config is not available
426
427 if (auto type = CFGetTypeID(csrConfig); type != CFDataGetTypeID()) {
428#ifndef QT_NO_DEBUG_STREAM
429 qWarning() << "Unexpected SIP config type" << CFCopyTypeIDDescription(type);
430#endif
431 return {};
432 }
433
434 QByteArray data = QByteArray::fromRawCFData(csrConfig.as<CFDataRef>());
435 if (data.size() != sizeof(uint32_t)) {
436 qWarning("Unexpected SIP config size %td", ptrdiff_t(data.size()));
437 return {};
438 }
439
440 return qFromLittleEndian<uint32_t>(data.constData());
441 }();
442 return configuration;
443}
444
445bool qt_mac_processHasEntitlement(const QString &entitlement)
446{
447 if (QCFType<SecTaskRef> task = SecTaskCreateFromSelf(kCFAllocatorDefault)) {
448 if (QCFType<CFTypeRef> value = SecTaskCopyValueForEntitlement(task,
449 entitlement.toCFString(), nullptr)) {
450
451 if (CFGetTypeID(value) != CFBooleanGetTypeID())
452 return false;
453
454 return CFBooleanGetValue(value.as<CFBooleanRef>());
455 }
456 }
457 return false;
458}
459
460#define CHECK_SPAWN(expr)
461 if ((expr) != 0) {
462 posix_spawnattr_destroy(&attr);
463 return;
464 }
465
466#ifdef QT_BUILD_INTERNAL
467void qt_mac_ensureResponsible()
468{
469#if !defined(QT_APPLE_NO_PRIVATE_APIS)
470 if (!responsibility_get_pid_responsible_for_pid || !responsibility_spawnattrs_setdisclaim)
471 return;
472
473 auto pid = getpid();
474 if (responsibility_get_pid_responsible_for_pid(pid) == pid)
475 return; // Already responsible
476
477 posix_spawnattr_t attr = {};
478 CHECK_SPAWN(posix_spawnattr_init(&attr));
479
480 // Behave as exec
481 short flags = POSIX_SPAWN_SETEXEC;
482
483 // Reset signal mask
484 sigset_t no_signals;
485 sigemptyset(&no_signals);
486 CHECK_SPAWN(posix_spawnattr_setsigmask(&attr, &no_signals));
487 flags |= POSIX_SPAWN_SETSIGMASK;
488
489 // Reset all signals to their default handlers
490 sigset_t all_signals;
491 sigfillset(&all_signals);
492 CHECK_SPAWN(posix_spawnattr_setsigdefault(&attr, &all_signals));
493 flags |= POSIX_SPAWN_SETSIGDEF;
494
495 CHECK_SPAWN(posix_spawnattr_setflags(&attr, flags));
496
497 CHECK_SPAWN(responsibility_spawnattrs_setdisclaim(&attr, 1));
498
499 char **argv = *_NSGetArgv();
500 posix_spawnp(&pid, argv[0], nullptr, &attr, argv, environ);
501 posix_spawnattr_destroy(&attr);
502#endif
503}
504#endif // QT_BUILD_INTERNAL
505
506#endif
507
509{
510 static bool isExtension = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSExtension"];
511 return isExtension;
512}
513
514/*!
515 \internal
516
517 Returns the bundle type of the item at \a path, or \c std::nullopt if it is
518 not a bundle.
519
520 Whether an item is a bundle is determined by its uniform type conforming to
521 com.apple.bundle, covering applications (.app), app extensions (.appex),
522 frameworks, and generic bundles. It is not determined by the mere presence
523 of a \c Contents directory or similar structure.
524
525 Directories whose extension is not a recognized bundle type, such as audio
526 unit components (.component), are still resolved as bundles when they carry
527 bundle metadata (a \c PkgInfo or \c Info.plist declaring the package type),
528 as the system then classifies them as a generic bundle. A bare directory
529 without such metadata is not considered a bundle.
530*/
531std::optional<UTType *> qt_apple_bundleType(const QString &path)
532{
533 NSURL *url = [NSURL fileURLWithPath:path.toNSString() isDirectory:YES];
534 UTType *type = nil;
535 if (![url getResourceValue:&type forKey:NSURLContentTypeKey error:nil] || !type)
536 return std::nullopt;
537 if (![type conformsToType:UTTypeBundle])
538 return std::nullopt;
539 return type;
540}
541
542#if !defined(QT_BOOTSTRAPPED) && !defined(Q_OS_WATCHOS)
544{
545 // Application extensions are not allowed to access the shared application
547 qWarning() << "accessing the shared" << [AppleApplication class]
548 << "is not allowed in application extensions";
549
550 // In practice the application is actually available, but the App
551 // review process will likely catch uses of it, so we return nil just
552 // in case, unless we don't care about being App Store compliant.
553#if QT_CONFIG(appstore_compliant)
554 return nil;
555#endif
556 }
557
558 // We use performSelector so that building with -fapplication-extension will
559 // not mistakenly think we're using the shared application in extensions.
560 return [[AppleApplication class] performSelector:@selector(sharedApplication)];
561}
562#endif
563
564#if !defined(QT_BOOTSTRAPPED)
565
567{
568#if defined(Q_OS_MACOS)
569 static bool isSandboxed = qt_mac_processHasEntitlement(u"com.apple.security.app-sandbox"_s);
570 return isSandboxed;
571#else
572 return true; // All other Apple platforms
573#endif
574}
575
576QT_END_NAMESPACE
577@implementation NSObject (QtExtras)
578- (id)qt_valueForPrivateKey:(NSString *)key
579{
580 if (qt_apple_isSandboxed())
581 return nil;
582
583 return [self valueForKey:key];
584}
585@end
586QT_BEGIN_NAMESPACE
587#endif // !QT_BOOTSTRAPPED
588
589#ifdef Q_OS_MACOS
590/*
591 Ensure that Objective-C objects auto-released in main(), directly or indirectly,
592 after QCoreApplication construction, are released when the app goes out of scope.
593 The memory will be reclaimed by the system either way when the process exits,
594 but by having a root level pool we ensure that the objects get their dealloc
595 methods called, which is useful for debugging object ownership graphs, etc.
596*/
597
598QT_END_NAMESPACE
599#define ROOT_LEVEL_POOL_MARKER QT_ROOT_LEVEL_POOL__THESE_OBJECTS_WILL_BE_RELEASED_WHEN_QAPP_GOES_OUT_OF_SCOPE
600@interface QT_MANGLE_NAMESPACE(ROOT_LEVEL_POOL_MARKER) : NSObject @end
601@implementation QT_MANGLE_NAMESPACE(ROOT_LEVEL_POOL_MARKER) @end
602QT_NAMESPACE_ALIAS_OBJC_CLASS(ROOT_LEVEL_POOL_MARKER);
603QT_BEGIN_NAMESPACE
604
605const char ROOT_LEVEL_POOL_DISABLE_SWITCH[] = "QT_DISABLE_ROOT_LEVEL_AUTORELEASE_POOL";
606
607QMacRootLevelAutoReleasePool::QMacRootLevelAutoReleasePool()
608{
609 if (qEnvironmentVariableIsSet(ROOT_LEVEL_POOL_DISABLE_SWITCH))
610 return;
611
612 pool.emplace();
613
614 [[[ROOT_LEVEL_POOL_MARKER alloc] init] autorelease];
615
616 if (qstrcmp(qgetenv("OBJC_DEBUG_MISSING_POOLS"), "YES") == 0) {
617 qDebug("QCoreApplication root level NSAutoreleasePool in place. Break on ~%s and use\n"
618 "'p [NSAutoreleasePool showPools]' to show leaked objects, or set %s",
619 __FUNCTION__, ROOT_LEVEL_POOL_DISABLE_SWITCH);
620 }
621}
622
623QMacRootLevelAutoReleasePool::~QMacRootLevelAutoReleasePool()
624{
625}
626#endif
627
628// -------------------------------------------------------------------------
629
630#ifndef QT_BOOTSTRAPPED
631void qt_apple_check_os_version()
632{
633#if defined(__WATCH_OS_VERSION_MIN_REQUIRED)
634 const char *os = "watchOS";
635 const int version = __WATCH_OS_VERSION_MIN_REQUIRED;
636#elif defined(__TV_OS_VERSION_MIN_REQUIRED)
637 const char *os = "tvOS";
638 const int version = __TV_OS_VERSION_MIN_REQUIRED;
639#elif defined(__VISION_OS_VERSION_MIN_REQUIRED)
640 const char *os = "visionOS";
641 const int version = __VISION_OS_VERSION_MIN_REQUIRED;
642#elif defined(__IPHONE_OS_VERSION_MIN_REQUIRED)
643 const char *os = "iOS";
644 const int version = __IPHONE_OS_VERSION_MIN_REQUIRED;
645#elif defined(__MAC_OS_X_VERSION_MIN_REQUIRED)
646 const char *os = "macOS";
647 const int version = __MAC_OS_X_VERSION_MIN_REQUIRED;
648#endif
649
650 const auto required = QVersionNumber(version / 10000, version / 100 % 100, version % 100);
651 const auto current = QOperatingSystemVersion::current().version();
652
653#if defined(Q_OS_MACOS)
654 // Check for compatibility version, in which case we can't do a
655 // comparison to the deployment target, which might be e.g. 11.0
656 if (current.majorVersion() == 10 && current.minorVersion() >= 16)
657 return;
658#endif
659
660 if (current < required) {
661 NSDictionary *plist = NSBundle.mainBundle.infoDictionary;
662 NSString *applicationName = plist[@"CFBundleDisplayName"];
663 if (!applicationName)
664 applicationName = plist[@"CFBundleName"];
665 if (!applicationName)
666 applicationName = NSProcessInfo.processInfo.processName;
667
668 fprintf(stderr, "Sorry, \"%s\" cannot be run on this version of %s. "
669 "Qt requires %s %ld.%ld.%ld or later, you have %s %ld.%ld.%ld.\n",
670 applicationName.UTF8String, os,
671 os, long(required.majorVersion()), long(required.minorVersion()), long(required.microVersion()),
672 os, long(current.majorVersion()), long(current.minorVersion()), long(current.microVersion()));
673
674 exit(1);
675 }
676}
678#endif // QT_BOOTSTRAPPED
679
680// -------------------------------------------------------------------------
681
682void QMacNotificationObserver::remove()
683{
684 if (observer)
685 [[NSNotificationCenter defaultCenter] removeObserver:observer];
686 observer = nullptr;
687}
688
689// -------------------------------------------------------------------------
690
691QMacKeyValueObserver::QMacKeyValueObserver(const QMacKeyValueObserver &other)
692 : QMacKeyValueObserver(other.object, other.keyPath, *other.callback.get())
693{
694}
695
696void QMacKeyValueObserver::addObserver(NSKeyValueObservingOptions options)
697{
698 [object addObserver:observer forKeyPath:keyPath options:options context:callback.get()];
699}
700
701void QMacKeyValueObserver::removeObserver() {
702 if (object)
703 [object removeObserver:observer forKeyPath:keyPath context:callback.get()];
704 object = nil;
705}
706
707KeyValueObserver *QMacKeyValueObserver::observer = [[KeyValueObserver alloc] init];
708
709QT_END_NAMESPACE
710@implementation QT_MANGLE_NAMESPACE(KeyValueObserver)
711- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
712 change:(NSDictionary<NSKeyValueChangeKey, id> *)change context:(void *)context
713{
714 Q_UNUSED(keyPath);
715 Q_UNUSED(object);
716 Q_UNUSED(change);
717
718 (*reinterpret_cast<QMacKeyValueObserver::Callback*>(context))();
719}
720@end
721QT_BEGIN_NAMESPACE
722
723// -------------------------------------------------------------------------
724
725#ifndef QT_BOOTSTRAPPED
726QOperatingSystemVersion QMacVersion::buildSDK(VersionTarget target)
727{
728 switch (target) {
729 case ApplicationBinary: return applicationVersion().second;
730 case QtLibraries: return libraryVersion().second;
731 }
732 Q_UNREACHABLE();
733}
734
735QOperatingSystemVersion QMacVersion::deploymentTarget(VersionTarget target)
736{
737 switch (target) {
738 case ApplicationBinary: return applicationVersion().first;
739 case QtLibraries: return libraryVersion().first;
740 }
741 Q_UNREACHABLE();
742}
743
744QOperatingSystemVersion QMacVersion::currentRuntime()
745{
746 return QOperatingSystemVersion::current();
747}
748
749// Mach-O platforms
750enum Platform {
751 macOS = 1,
752 iOS = 2,
753 tvOS = 3,
754 watchOS = 4,
755 bridgeOS = 5,
756 macCatalyst = 6,
757 iOSSimulator = 7,
758 tvOSSimulator = 8,
759 watchOSSimulator = 9
760};
761
762QMacVersion::VersionTuple QMacVersion::versionsForImage(const mach_header *machHeader)
763{
764 static auto osForLoadCommand = [](uint32_t cmd) {
765 switch (cmd) {
766 case LC_VERSION_MIN_MACOSX: return QOperatingSystemVersion::MacOS;
767 case LC_VERSION_MIN_IPHONEOS: return QOperatingSystemVersion::IOS;
768 case LC_VERSION_MIN_TVOS: return QOperatingSystemVersion::TvOS;
769 case LC_VERSION_MIN_WATCHOS: return QOperatingSystemVersion::WatchOS;
770 default: return QOperatingSystemVersion::Unknown;
771 }
772 };
773
774 static auto osForPlatform = [](uint32_t platform) {
775 switch (platform) {
776 case Platform::macOS:
777 return QOperatingSystemVersion::MacOS;
778 case Platform::iOS:
779 case Platform::iOSSimulator:
780 return QOperatingSystemVersion::IOS;
781 case Platform::tvOS:
782 case Platform::tvOSSimulator:
783 return QOperatingSystemVersion::TvOS;
784 case Platform::watchOS:
785 case Platform::watchOSSimulator:
786 return QOperatingSystemVersion::WatchOS;
787 default:
788 return QOperatingSystemVersion::Unknown;
789 }
790 };
791
792 static auto makeVersionTuple = [](uint32_t dt, uint32_t sdk, QOperatingSystemVersion::OSType osType) {
793 return std::pair(
794 QOperatingSystemVersion(osType, dt >> 16 & 0xffff, dt >> 8 & 0xff, dt & 0xff),
795 QOperatingSystemVersion(osType, sdk >> 16 & 0xffff, sdk >> 8 & 0xff, sdk & 0xff)
796 );
797 };
798
799 const bool is64Bit = machHeader->magic == MH_MAGIC_64 || machHeader->magic == MH_CIGAM_64;
800 auto commandCursor = uintptr_t(machHeader) + (is64Bit ? sizeof(mach_header_64) : sizeof(mach_header));
801
802 for (uint32_t i = 0; i < machHeader->ncmds; ++i) {
803 load_command *loadCommand = reinterpret_cast<load_command *>(commandCursor);
804 if (loadCommand->cmd == LC_VERSION_MIN_MACOSX || loadCommand->cmd == LC_VERSION_MIN_IPHONEOS
805 || loadCommand->cmd == LC_VERSION_MIN_TVOS || loadCommand->cmd == LC_VERSION_MIN_WATCHOS) {
806 auto versionCommand = reinterpret_cast<version_min_command *>(loadCommand);
807 return makeVersionTuple(versionCommand->version, versionCommand->sdk, osForLoadCommand(loadCommand->cmd));
808 } else if (loadCommand->cmd == LC_BUILD_VERSION) {
809 auto versionCommand = reinterpret_cast<build_version_command *>(loadCommand);
810 return makeVersionTuple(versionCommand->minos, versionCommand->sdk, osForPlatform(versionCommand->platform));
811 }
812 commandCursor += loadCommand->cmdsize;
813 }
814 Q_ASSERT_X(false, "QMacVersion", "Could not find any version load command");
815 Q_UNREACHABLE();
816}
817
818QMacVersion::VersionTuple QMacVersion::applicationVersion()
819{
820 static VersionTuple version = []() {
821 const mach_header *executableHeader = nullptr;
822 for (uint32_t i = 0; i < _dyld_image_count(); ++i) {
823 auto header = _dyld_get_image_header(i);
824 if (header->filetype == MH_EXECUTE) {
825 executableHeader = header;
826 break;
827 }
828 }
829 Q_ASSERT_X(executableHeader, "QMacVersion", "Failed to resolve Mach-O header of executable");
830 return versionsForImage(executableHeader);
831 }();
832 return version;
833}
834
835QMacVersion::VersionTuple QMacVersion::libraryVersion()
836{
837 static VersionTuple version = []() {
838 Dl_info qtCoreImage;
839 dladdr((const void *)&QMacVersion::libraryVersion, &qtCoreImage);
840 Q_ASSERT_X(qtCoreImage.dli_fbase, "QMacVersion", "Failed to resolve Mach-O header of QtCore");
841 return versionsForImage(static_cast<mach_header*>(qtCoreImage.dli_fbase));
842 }();
843 return version;
844}
845#endif // QT_BOOTSTRAPPED
846
847// -------------------------------------------------------------------------
848
849QObjCWeakPointerBase::QObjCWeakPointerBase(NSObject *object)
850 : m_weakReference(object)
851{
852}
853
854QObjCWeakPointerBase::QObjCWeakPointerBase(const QObjCWeakPointerBase &other)
855{
856 QMacAutoReleasePool pool;
857 m_weakReference = other.m_weakReference;
858}
859
860QObjCWeakPointerBase &QObjCWeakPointerBase::operator=(const QObjCWeakPointerBase &other)
861{
862 QMacAutoReleasePool pool;
863 m_weakReference = other.m_weakReference;
864 return *this;
865}
866
867QObjCWeakPointerBase::~QObjCWeakPointerBase()
868{
869 QMacAutoReleasePool pool;
870 m_weakReference = nil;
871}
872
873NSObject *QObjCWeakPointerBase::get() const
874{
875 // Loading from a __weak variable will retain and auto-release (in non-ARC).
876 // Unlike cases above, we want the object to stay alive until the outer
877 // auto-release pool is drained, so that consumers of QObjCWeakPointer
878 // can trust that the variable they get back will be alive, similar to
879 // the semantics of loading from __weak.
880 return m_weakReference;
881}
882
883// -------------------------------------------------------------------------
884
885QT_END_NAMESPACE
\inmodule QtCore
Definition qmargins.h:27
QT_FOR_EACH_CORE_FOUNDATION_TYPE(QT_DECLARE_WEAK_QDEBUG_OPERATOR_FOR_CF_TYPE)
int csr_get_active_config(csr_config_t *) __attribute__((weak_import))
uint32_t csr_config_t
Definition qcore_mac.mm:44
void objc_autoreleasePoolPop(void *pool)
std::optional< UTType * > qt_apple_bundleType(const QString &path)
Definition qcore_mac.mm:531
QT_END_NAMESPACE QT_USE_NAMESPACE void * objc_autoreleasePoolPush(void)
QDebug operator<<(QDebug dbg, const NSObject *nsObject)
Definition qcore_mac.mm:209
AppleApplication * qt_apple_sharedApplication()
Definition qcore_mac.mm:543
Q_CONSTRUCTOR_FUNCTION(qt_apple_check_os_version)
Q_CORE_EXPORT bool qt_apple_isApplicationExtension()
Definition qcore_mac.mm:508
Q_CORE_EXPORT bool qt_apple_isSandboxed()
Definition qcore_mac.mm:566
QDebug operator<<(QDebug debug, QDir::Filters filters)
Definition qdir.cpp:2598
QDebug operator<<(QDebug dbg, const QFileInfo &fi)