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