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 ApplicationState:
265 case ApplicationIcon:
266 case BackingStoreStaticContents:
267 case OffscreenSurface:
268 return true;
269 case ScreenWindowGrabbing: {
270#if defined(Q_PROCESSOR_X86_64)
271 // ScreenCaptureKit brings down WindowServer on macOS 14 x86_64 VMs in CI
272 if (QOperatingSystemVersion::current() <= QOperatingSystemVersion::MacOSSonoma
273 && qEnvironmentVariable("QTEST_ENVIRONMENT") == "ci") {
274 return false;
275 }
276#endif
277 return true;
278 }
279 case MouseCursorPositioning:
280 return AXIsProcessTrustedWithOptions(CFDictionaryRef(@{
281 id(kAXTrustedCheckOptionPrompt): @NO
282 }));
283 default:
284 return QPlatformIntegration::hasCapability(cap);
285 }
286}
287
288QPlatformWindow *QCocoaIntegration::createPlatformWindow(QWindow *window) const
289{
290 return new QCocoaWindow(window);
291}
292
293QPlatformWindow *QCocoaIntegration::createForeignWindow(QWindow *window, WId nativeHandle) const
294{
295 return new QCocoaWindow(window, nativeHandle);
296}
297
298class QCocoaOffscreenSurface : public QPlatformOffscreenSurface
299{
300public:
301 QCocoaOffscreenSurface(QOffscreenSurface *offscreenSurface) : QPlatformOffscreenSurface(offscreenSurface) {}
302
303 QSurfaceFormat format() const override
304 {
305 Q_ASSERT(offscreenSurface());
306 return offscreenSurface()->requestedFormat();
307 }
308 bool isValid() const override { return true; }
309};
310
311QPlatformOffscreenSurface *QCocoaIntegration::createPlatformOffscreenSurface(QOffscreenSurface *surface) const
312{
313 return new QCocoaOffscreenSurface(surface);
314}
315
316#ifndef QT_NO_OPENGL
317QPlatformOpenGLContext *QCocoaIntegration::createPlatformOpenGLContext(QOpenGLContext *context) const
318{
319 return new QCocoaGLContext(context);
320}
321
322QOpenGLContext *QCocoaIntegration::createOpenGLContext(NSOpenGLContext *nativeContext, QOpenGLContext *shareContext) const
323{
324 if (!nativeContext)
325 return nullptr;
326
327 auto *context = new QOpenGLContext;
328 context->setShareContext(shareContext);
329 auto *contextPrivate = QOpenGLContextPrivate::get(context);
330 contextPrivate->adopt(new QCocoaGLContext(nativeContext));
331 return context;
332}
333
334#endif
335
336QPlatformBackingStore *QCocoaIntegration::createPlatformBackingStore(QWindow *window) const
337{
338 QMacAutoReleasePool pool;
339
340 QCocoaWindow *platformWindow = static_cast<QCocoaWindow*>(window->handle());
341 if (!platformWindow) {
342 qWarning() << window << "must be created before being used with a backingstore";
343 return nullptr;
344 }
345
346 switch (window->surfaceType()) {
347 case QSurface::RasterSurface:
348 return new QCALayerBackingStore(window);
349 case QSurface::MetalSurface:
350 case QSurface::OpenGLSurface:
351 case QSurface::VulkanSurface:
352 // If the window is a widget window, we know that the QWidgetRepaintManager
353 // will explicitly use rhiFlush() for the window owning the backingstore,
354 // and any child window with the same surface format. This means we can
355 // safely return a QCALayerBackingStore here, to ensure that any plain
356 // flush() for child windows that don't have a matching surface format
357 // will still work, by setting the layer's contents property.
358 if (window->inherits("QWidgetWindow"))
359 return new QCALayerBackingStore(window);
360
361 // Otherwise we return a QRhiBackingStore, that implements flush() in
362 // terms of rhiFlush().
363 return new QRhiBackingStore(window);
364 default:
365 return nullptr;
366 }
367}
368
369QAbstractEventDispatcher *QCocoaIntegration::createEventDispatcher() const
370{
371 return new QCocoaEventDispatcher;
372}
373
374#if QT_CONFIG(vulkan)
375QPlatformVulkanInstance *QCocoaIntegration::createPlatformVulkanInstance(QVulkanInstance *instance) const
376{
377 mCocoaVulkanInstance = new QCocoaVulkanInstance(instance);
378 return mCocoaVulkanInstance;
379}
380
381QCocoaVulkanInstance *QCocoaIntegration::getCocoaVulkanInstance() const
382{
383 return mCocoaVulkanInstance;
384}
385#endif
386
387QCoreTextFontDatabase *QCocoaIntegration::fontDatabase() const
388{
389 return mFontDb.data();
390}
391
392QCocoaNativeInterface *QCocoaIntegration::nativeInterface() const
393{
394 return mNativeInterface.data();
395}
396
397QPlatformInputContext *QCocoaIntegration::inputContext() const
398{
399 return mInputContext.data();
400}
401
402#if QT_CONFIG(accessibility)
403QCocoaAccessibility *QCocoaIntegration::accessibility() const
404{
405 return mAccessibility.data();
406}
407#endif
408
409#ifndef QT_NO_CLIPBOARD
410QCocoaClipboard *QCocoaIntegration::clipboard() const
411{
412 return mCocoaClipboard;
413}
414#endif
415
416QCocoaDrag *QCocoaIntegration::drag() const
417{
418 return mCocoaDrag.data();
419}
420
421QStringList QCocoaIntegration::themeNames() const
422{
423 return QStringList(QLatin1StringView(QCocoaTheme::name));
424}
425
426QPlatformTheme *QCocoaIntegration::createPlatformTheme(const QString &name) const
427{
428 if (name == QLatin1StringView(QCocoaTheme::name))
429 return new QCocoaTheme;
430 return QPlatformIntegration::createPlatformTheme(name);
431}
432
433QCocoaServices *QCocoaIntegration::services() const
434{
435 if (mServices.isNull())
436 mServices.reset(new QCocoaServices);
437
438 return mServices.data();
439}
440
441QVariant QCocoaIntegration::styleHint(StyleHint hint) const
442{
443 switch (hint) {
444 case FontSmoothingGamma:
445 return QCoreTextFontEngine::fontSmoothingGamma();
446 case ShowShortcutsInContextMenus:
447 return QVariant(false);
448 case ReplayMousePressOutsidePopup:
449 return QVariant(false);
450 default: break;
451 }
452
453 return QPlatformIntegration::styleHint(hint);
454}
455
456QPlatformKeyMapper *QCocoaIntegration::keyMapper() const
457{
458 return mKeyboardMapper.data();
459}
460
461void QCocoaIntegration::setApplicationIcon(const QIcon &icon) const
462{
463 if (icon.isNull()) {
464 NSApp.applicationIconImage = nil;
465 return;
466 }
467
468 // Request a size that looks good on the highest resolution screen available
469 // for icon engines that don't have an intrinsic size (like SVG).
470 const auto dockTitleSize = QSizeF::fromCGSize(NSApp.dockTile.size).toSize();
471 auto image = icon.pixmap(dockTitleSize, qGuiApp->devicePixelRatio()).toImage();
472
473 // The assigned image is scaled by the system to fit into the tile,
474 // but without taking aspect ratio into account, so let's pad the
475 // image up front if it's not already square.
476 image = qt_mac_padToSquareImage(image);
477
478 NSApp.applicationIconImage = [NSImage imageFromQImage:image];
479}
480
481void QCocoaIntegration::setApplicationBadge(qint64 number)
482{
483 NSApp.dockTile.badgeLabel = number ? [NSString stringWithFormat:@"%" PRId64, number] : nil;
484}
485
486void QCocoaIntegration::beep() const
487{
488 NSBeep();
489}
490
491void QCocoaIntegration::quit() const
492{
493 qCDebug(lcQpaApplication) << "Terminating application";
494 [NSApp terminate:nil];
495}
496
497void QCocoaIntegration::focusWindowChanged(QWindow *focusWindow)
498{
499 // Don't revert icon just because we lost focus
500 if (!focusWindow)
501 return;
502
503 static bool hasDefaultApplicationIcon = [](){
504 NSImage *genericApplicationIcon = [NSWorkspace.sharedWorkspace
505 iconForContentType:UTTypeApplicationBundle];
506 NSImage *applicationIcon = [NSImage imageNamed:NSImageNameApplicationIcon];
507
508 NSRect rect = NSMakeRect(0, 0, 32, 32);
509 return [applicationIcon CGImageForProposedRect:&rect context:nil hints:nil]
510 == [genericApplicationIcon CGImageForProposedRect:&rect context:nil hints:nil];
511 }();
512
513 // Don't let the window icon override an explicit application icon set in the Info.plist
514 if (!hasDefaultApplicationIcon)
515 return;
516
517 // Or an explicit application icon set on QGuiApplication
518 if (!qGuiApp->windowIcon().isNull())
519 return;
520
521 setApplicationIcon(focusWindow->icon());
522}
523
524QT_END_NAMESPACE
525
526#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")