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