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