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