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
qcocoaintegration.mm
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
5#include <AppKit/AppKit.h>
6
8
9#include "qcocoawindow.h"
14#include "qcocoahelpers.h"
17#include "qcocoatheme.h"
21#include "qcocoascreen.h"
22#if QT_CONFIG(sessionmanager)
23# include "qcocoasessionmanager.h"
24#endif
26
27#include <qpa/qplatforminputcontextfactory_p.h>
28#include <qpa/qplatformaccessibility.h>
29#include <qpa/qplatforminputcontextfactory_p.h>
30#include <qpa/qplatformoffscreensurface.h>
31#include <QtCore/qcoreapplication.h>
32#include <QtGui/qpointingdevice.h>
33
34#include <QtCore/private/qcore_mac_p.h>
35#include <QtGui/private/qcoregraphics_p.h>
36#include <QtGui/private/qmacmimeregistry_p.h>
37#ifndef QT_NO_OPENGL
38# include <QtGui/private/qopenglcontext_p.h>
39#endif
40#include <QtGui/private/qrhibackingstore_p.h>
41#include <QtGui/private/qfontengine_coretext_p.h>
42
43#include <IOKit/graphics/IOGraphicsLib.h>
44#include <UniformTypeIdentifiers/UTCoreTypes.h>
45
46#include <inttypes.h>
47
48static void initResources()
49{
50 Q_INIT_RESOURCE(qcocoaresources);
51}
52
53QT_BEGIN_NAMESPACE
54
55using namespace Qt::StringLiterals;
56
57Q_LOGGING_CATEGORY(lcQpa, "qt.qpa", QtWarningMsg);
58
60{
61 if (!lcQpa().isInfoEnabled())
62 return;
63
64 auto osVersion = QMacVersion::currentRuntime();
65 auto qtBuildSDK = QMacVersion::buildSDK(QMacVersion::QtLibraries);
66 auto qtDeploymentTarget = QMacVersion::deploymentTarget(QMacVersion::QtLibraries);
67 auto appBuildSDK = QMacVersion::buildSDK(QMacVersion::ApplicationBinary);
68 auto appDeploymentTarget = QMacVersion::deploymentTarget(QMacVersion::ApplicationBinary);
69
70 qCInfo(lcQpa, "Loading macOS (Cocoa) platform plugin for Qt " QT_VERSION_STR ", running on macOS %d.%d.%d\n\n"
71 " Component SDK version Deployment target \n"
72 " ------------- ------------- -------------------\n"
73 " Qt " QT_VERSION_STR " %d.%d.%d %d.%d.%d\n"
74 " Application %d.%d.%d %d.%d.%d\n",
75 osVersion.majorVersion(), osVersion.minorVersion(), osVersion.microVersion(),
76 qtBuildSDK.majorVersion(), qtBuildSDK.minorVersion(), qtBuildSDK.microVersion(),
77 qtDeploymentTarget.majorVersion(), qtDeploymentTarget.minorVersion(), qtDeploymentTarget.microVersion(),
78 appBuildSDK.majorVersion(), appBuildSDK.minorVersion(), appBuildSDK.microVersion(),
79 appDeploymentTarget.majorVersion(), appDeploymentTarget.minorVersion(), appDeploymentTarget.microVersion());
80
81 qCInfo(lcQpa) << "Running with Liquid Glass enabled:" << qt_apple_runningWithLiquidGlass();
82}
83
84
85class QCoreTextFontEngine;
86class QFontEngineFT;
87
88static QCocoaIntegration::Options parseOptions(const QStringList &paramList)
89{
90 QCocoaIntegration::Options options;
91 for (const QString &param : paramList) {
92#ifndef QT_NO_FREETYPE
93 if (param == "fontengine=freetype"_L1)
94 options |= QCocoaIntegration::UseFreeTypeFontEngine;
95 else
96#endif
97 qWarning() << "Unknown option" << param;
98 }
99 return options;
100}
101
102QCocoaIntegration *QCocoaIntegration::mInstance = nullptr;
103
104QCocoaIntegration::QCocoaIntegration(const QStringList &paramList)
105 : mOptions(parseOptions(paramList))
106 , mFontDb(nullptr)
107#if QT_CONFIG(accessibility)
108 , mAccessibility(new QCocoaAccessibility)
109#endif
110#ifndef QT_NO_CLIPBOARD
111 , mCocoaClipboard(new QCocoaClipboard)
112#endif
113 , mCocoaDrag(new QCocoaDrag)
114 , mNativeInterface(new QCocoaNativeInterface)
115 , mKeyboardMapper(new QAppleKeyMapper)
116{
117 logVersionInformation();
118
119 if (mInstance)
120 qWarning("Creating multiple Cocoa platform integrations is not supported");
121 mInstance = this;
122
123#ifndef QT_NO_FREETYPE
124 if (mOptions.testFlag(UseFreeTypeFontEngine))
125 mFontDb.reset(new QCoreTextFontDatabaseEngineFactory<QFontEngineFT>);
126 else
127#endif
128 mFontDb.reset(new QCoreTextFontDatabaseEngineFactory<QCoreTextFontEngine>);
129
130 auto icStrs = QPlatformInputContextFactory::requested();
131 icStrs.isEmpty() ? mInputContext.reset(new QCocoaInputContext)
132 : mInputContext.reset(QPlatformInputContextFactory::create(icStrs));
133
134 initResources();
135 QMacAutoReleasePool pool;
136
137 NSApplication *cocoaApplication = [QNSApplication sharedApplication];
138 qt_redirectNSApplicationSendEvent();
139
140 if (qEnvironmentVariableIsEmpty("QT_MAC_DISABLE_FOREGROUND_APPLICATION_TRANSFORM")) {
141 // Applications launched from plain executables (without an app
142 // bundle) are "background" applications that does not take keyboard
143 // focus or have a dock icon or task switcher entry. Qt Gui apps generally
144 // wants to be foreground applications so change the process type. (But
145 // see the function implementation for exceptions.)
146 qt_mac_transformProccessToForegroundApplication();
147 }
148
149 // Qt 4 also does not set the application delegate, so that behavior
150 // is matched here.
151 if (!QCoreApplication::testAttribute(Qt::AA_PluginApplication)) {
152
153 // Set app delegate, link to the current delegate (if any)
154 QCocoaApplicationDelegate *newDelegate = [QCocoaApplicationDelegate sharedDelegate];
155 [newDelegate setReflectionDelegate:[cocoaApplication delegate]];
156 [cocoaApplication setDelegate:newDelegate];
157
158 // Load the application menu. This menu contains Preferences, Hide, Quit.
159 QCocoaMenuLoader *qtMenuLoader = [QCocoaMenuLoader sharedMenuLoader];
160 [cocoaApplication setMenu:[qtMenuLoader menu]];
161 }
162
163 QCocoaScreen::initializeScreens();
164
165 QMacMimeRegistry::initializeMimeTypes();
166 QCocoaMimeTypes::initializeMimeTypes();
167 QWindowSystemInterfacePrivate::TabletEvent::setPlatformSynthesizesMouse(false);
168 QWindowSystemInterface::registerInputDevice(new QInputDevice(QString("keyboard"), 0,
169 QInputDevice::DeviceType::Keyboard, QString(), this));
170
171 connect(qGuiApp, &QGuiApplication::focusWindowChanged,
172 this, &QCocoaIntegration::focusWindowChanged);
173
174 // Opening of a native menu should close all popup windows
175 m_menuTrackingObserver = QMacNotificationObserver(nil,
176 NSMenuDidBeginTrackingNotification, ^{
177 QGuiApplicationPrivate::instance()->closeAllPopups();
178 });
179}
180
181QCocoaIntegration::~QCocoaIntegration()
182{
183 mInstance = nullptr;
184
185 qt_resetNSApplicationSendEvent();
186
187 QMacAutoReleasePool pool;
188 if (!QCoreApplication::testAttribute(Qt::AA_PluginApplication)) {
189 // remove the apple event handlers installed by QCocoaApplicationDelegate
190 QCocoaApplicationDelegate *delegate = [QCocoaApplicationDelegate sharedDelegate];
191 [delegate removeAppleEventHandlers];
192 // reset the application delegate
193 [[NSApplication sharedApplication] setDelegate:nil];
194 }
195
196 // Stop global mouse event and app activation monitoring
197 QCocoaWindow::removePopupMonitor();
198
199#ifndef QT_NO_CLIPBOARD
200 // Delete the clipboard integration and destroy mime type converters.
201 // Deleting the clipboard integration flushes promised pastes using
202 // the mime converters - the ordering here is important.
203 delete mCocoaClipboard;
204 QMacMimeRegistry::destroyMimeTypes();
205#endif
206
207 QCocoaScreen::cleanupScreens();
208}
209
210QCocoaIntegration *QCocoaIntegration::instance()
211{
212 return mInstance;
213}
214
215QCocoaIntegration::Options QCocoaIntegration::options() const
216{
217 return mOptions;
218}
219
220#if QT_CONFIG(sessionmanager)
221QPlatformSessionManager *QCocoaIntegration::createPlatformSessionManager(const QString &id, const QString &key) const
222{
223 return new QCocoaSessionManager(id, key);
224}
225#endif
226
227bool QCocoaIntegration::hasCapability(QPlatformIntegration::Capability cap) const
228{
229 switch (cap) {
230#ifndef QT_NO_OPENGL
231 case ThreadedOpenGL:
232 // AppKit expects rendering to happen on the main thread, and we can
233 // easily end up in situations where rendering on secondary threads
234 // will result in visual artifacts, bugs, or even deadlocks, when
235 // layer-backed.
236 return false;
237 case OpenGL:
238#if defined(Q_PROCESSOR_ARM)
239 if (QOperatingSystemVersion::current() >= QOperatingSystemVersion::MacOSTahoe) {
240 // Tahoe has issues with software-backed GL, crashing in common operations
241 static bool isSoftwareContext = []{
242 QOpenGLContext context;
243 context.create();
244 auto *cocoaContext = static_cast<QCocoaGLContext*>(context.handle());
245 if (cocoaContext->isSoftwareContext()) {
246 qWarning() << "Detected software OpenGL backend,"
247 << "which is known to be broken on"
248 << qUtf8Printable(QSysInfo::prettyProductName());
249 return true;
250 } else {
251 return false;
252 }
253 }();
254 return !isSoftwareContext;
255 }
256#endif
257 Q_FALLTHROUGH();
258 case BufferQueueingOpenGL:
259#endif
260 case ThreadedPixmaps:
261 case WindowMasks:
262 case MultipleWindows:
263 case ForeignWindows:
264 case ChildWindows:
265 case ApplicationState:
266 case ApplicationIcon:
267 case BackingStoreStaticContents:
268 case OffscreenSurface:
269 return true;
270 case ScreenWindowGrabbing: {
271#if defined(Q_PROCESSOR_X86_64)
272 // ScreenCaptureKit brings down WindowServer on macOS 14 x86_64 VMs in CI
273 if (QOperatingSystemVersion::current() <= QOperatingSystemVersion::MacOSSonoma
274 && qEnvironmentVariable("QTEST_ENVIRONMENT") == "ci") {
275 return false;
276 }
277#endif
278 return true;
279 }
280 case MouseCursorPositioning:
281 return AXIsProcessTrustedWithOptions(CFDictionaryRef(@{
282 id(kAXTrustedCheckOptionPrompt): @NO
283 }));
284 default:
285 return QPlatformIntegration::hasCapability(cap);
286 }
287}
288
289QPlatformWindow *QCocoaIntegration::createPlatformWindow(QWindow *window) const
290{
291 return new QCocoaWindow(window);
292}
293
294QPlatformWindow *QCocoaIntegration::createForeignWindow(QWindow *window, WId nativeHandle) const
295{
296 return new QCocoaWindow(window, nativeHandle);
297}
298
299class QCocoaOffscreenSurface : public QPlatformOffscreenSurface
300{
301public:
302 QCocoaOffscreenSurface(QOffscreenSurface *offscreenSurface) : QPlatformOffscreenSurface(offscreenSurface) {}
303
304 QSurfaceFormat format() const override
305 {
306 Q_ASSERT(offscreenSurface());
307 return offscreenSurface()->requestedFormat();
308 }
309 bool isValid() const override { return true; }
310};
311
312QPlatformOffscreenSurface *QCocoaIntegration::createPlatformOffscreenSurface(QOffscreenSurface *surface) const
313{
314 return new QCocoaOffscreenSurface(surface);
315}
316
317#ifndef QT_NO_OPENGL
318QPlatformOpenGLContext *QCocoaIntegration::createPlatformOpenGLContext(QOpenGLContext *context) const
319{
320 return new QCocoaGLContext(context);
321}
322
323QOpenGLContext *QCocoaIntegration::createOpenGLContext(NSOpenGLContext *nativeContext, QOpenGLContext *shareContext) const
324{
325 if (!nativeContext)
326 return nullptr;
327
328 auto *context = new QOpenGLContext;
329 context->setShareContext(shareContext);
330 auto *contextPrivate = QOpenGLContextPrivate::get(context);
331 contextPrivate->adopt(new QCocoaGLContext(nativeContext));
332 return context;
333}
334
335#endif
336
337QPlatformBackingStore *QCocoaIntegration::createPlatformBackingStore(QWindow *window) const
338{
339 QMacAutoReleasePool pool;
340
341 QCocoaWindow *platformWindow = static_cast<QCocoaWindow*>(window->handle());
342 if (!platformWindow) {
343 qWarning() << window << "must be created before being used with a backingstore";
344 return nullptr;
345 }
346
347 switch (window->surfaceType()) {
348 case QSurface::RasterSurface:
349 return new QCALayerBackingStore(window);
350 case QSurface::MetalSurface:
351 case QSurface::OpenGLSurface:
352 case QSurface::VulkanSurface:
353 // If the window is a widget window, we know that the QWidgetRepaintManager
354 // will explicitly use rhiFlush() for the window owning the backingstore,
355 // and any child window with the same surface format. This means we can
356 // safely return a QCALayerBackingStore here, to ensure that any plain
357 // flush() for child windows that don't have a matching surface format
358 // will still work, by setting the layer's contents property.
359 if (window->inherits("QWidgetWindow"))
360 return new QCALayerBackingStore(window);
361
362 // Otherwise we return a QRhiBackingStore, that implements flush() in
363 // terms of rhiFlush().
364 return new QRhiBackingStore(window);
365 default:
366 return nullptr;
367 }
368}
369
370QAbstractEventDispatcher *QCocoaIntegration::createEventDispatcher() const
371{
372 return new QCocoaEventDispatcher;
373}
374
375#if QT_CONFIG(vulkan)
376QPlatformVulkanInstance *QCocoaIntegration::createPlatformVulkanInstance(QVulkanInstance *instance) const
377{
378 mCocoaVulkanInstance = new QCocoaVulkanInstance(instance);
379 return mCocoaVulkanInstance;
380}
381
382QCocoaVulkanInstance *QCocoaIntegration::getCocoaVulkanInstance() const
383{
384 return mCocoaVulkanInstance;
385}
386#endif
387
388QCoreTextFontDatabase *QCocoaIntegration::fontDatabase() const
389{
390 return mFontDb.data();
391}
392
393QCocoaNativeInterface *QCocoaIntegration::nativeInterface() const
394{
395 return mNativeInterface.data();
396}
397
398QPlatformInputContext *QCocoaIntegration::inputContext() const
399{
400 return mInputContext.data();
401}
402
403#if QT_CONFIG(accessibility)
404QCocoaAccessibility *QCocoaIntegration::accessibility() const
405{
406 return mAccessibility.data();
407}
408#endif
409
410#ifndef QT_NO_CLIPBOARD
411QCocoaClipboard *QCocoaIntegration::clipboard() const
412{
413 return mCocoaClipboard;
414}
415#endif
416
417QCocoaDrag *QCocoaIntegration::drag() const
418{
419 return mCocoaDrag.data();
420}
421
422QStringList QCocoaIntegration::themeNames() const
423{
424 return QStringList(QLatin1StringView(QCocoaTheme::name));
425}
426
427QPlatformTheme *QCocoaIntegration::createPlatformTheme(const QString &name) const
428{
429 if (name == QLatin1StringView(QCocoaTheme::name))
430 return new QCocoaTheme;
431 return QPlatformIntegration::createPlatformTheme(name);
432}
433
434QCocoaServices *QCocoaIntegration::services() const
435{
436 if (mServices.isNull())
437 mServices.reset(new QCocoaServices);
438
439 return mServices.data();
440}
441
442QVariant QCocoaIntegration::styleHint(StyleHint hint) const
443{
444 switch (hint) {
445 case FontSmoothingGamma:
446 return QCoreTextFontEngine::fontSmoothingGamma();
447 case ShowShortcutsInContextMenus:
448 return QVariant(false);
449 case ReplayMousePressOutsidePopup:
450 return QVariant(false);
451 default: break;
452 }
453
454 return QPlatformIntegration::styleHint(hint);
455}
456
457QPlatformKeyMapper *QCocoaIntegration::keyMapper() const
458{
459 return mKeyboardMapper.data();
460}
461
462void QCocoaIntegration::setApplicationIcon(const QIcon &icon) const
463{
464 if (icon.isNull()) {
465 NSApp.applicationIconImage = nil;
466 return;
467 }
468
469 // Request a size that looks good on the highest resolution screen available
470 // for icon engines that don't have an intrinsic size (like SVG).
471 const auto dockTitleSize = QSizeF::fromCGSize(NSApp.dockTile.size).toSize();
472 auto image = icon.pixmap(dockTitleSize, qGuiApp->devicePixelRatio()).toImage();
473
474 // The assigned image is scaled by the system to fit into the tile,
475 // but without taking aspect ratio into account, so let's pad the
476 // image up front if it's not already square.
477 image = qt_mac_padToSquareImage(image);
478
479 NSApp.applicationIconImage = [NSImage imageFromQImage:image];
480}
481
482void QCocoaIntegration::setApplicationBadge(qint64 number)
483{
484 NSApp.dockTile.badgeLabel = number ? [NSString stringWithFormat:@"%" PRId64, number] : nil;
485}
486
487void QCocoaIntegration::beep() const
488{
489 NSBeep();
490}
491
492void QCocoaIntegration::quit() const
493{
494 qCDebug(lcQpaApplication) << "Terminating application";
495 [NSApp terminate:nil];
496}
497
498void QCocoaIntegration::focusWindowChanged(QWindow *focusWindow)
499{
500 // Don't revert icon just because we lost focus
501 if (!focusWindow)
502 return;
503
504 static bool hasDefaultApplicationIcon = [](){
505 NSImage *genericApplicationIcon = [NSWorkspace.sharedWorkspace
506 iconForContentType:UTTypeApplicationBundle];
507 NSImage *applicationIcon = [NSImage imageNamed:NSImageNameApplicationIcon];
508
509 NSRect rect = NSMakeRect(0, 0, 32, 32);
510 return [applicationIcon CGImageForProposedRect:&rect context:nil hints:nil]
511 == [genericApplicationIcon CGImageForProposedRect:&rect context:nil hints:nil];
512 }();
513
514 // Don't let the window icon override an explicit application icon set in the Info.plist
515 if (!hasDefaultApplicationIcon)
516 return;
517
518 // Or an explicit application icon set on QGuiApplication
519 if (!qGuiApp->windowIcon().isNull())
520 return;
521
522 setApplicationIcon(focusWindow->icon());
523}
524
525QT_END_NAMESPACE
526
527#include "moc_qcocoaintegration.cpp"
static const char * name
Definition qcocoatheme.h:48
friend class QFontEngineFT
Definition qpainter.h:435
static QCocoaIntegration::Options parseOptions(const QStringList &paramList)
static void initResources()
static void logVersionInformation()
Q_LOGGING_CATEGORY(lcEventDispatcher, "qt.eventdispatcher")