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