Qt 6.x
The Qt SDK
Loading...
Searching...
No Matches
qcocoaapplicationdelegate.mm
Go to the documentation of this file.
1// Copyright (C) 2018 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
4/****************************************************************************
5 **
6 ** Copyright (c) 2007-2008, Apple, Inc.
7 **
8 ** All rights reserved.
9 **
10 ** Redistribution and use in source and binary forms, with or without
11 ** modification, are permitted provided that the following conditions are met:
12 **
13 ** * Redistributions of source code must retain the above copyright notice,
14 ** this list of conditions and the following disclaimer.
15 **
16 ** * Redistributions in binary form must reproduce the above copyright notice,
17 ** this list of conditions and the following disclaimer in the documentation
18 ** and/or other materials provided with the distribution.
19 **
20 ** * Neither the name of Apple, Inc. nor the names of its contributors
21 ** may be used to endorse or promote products derived from this software
22 ** without specific prior written permission.
23 **
24 ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
25 ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
26 ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
27 ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
28 ** CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
29 ** EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
30 ** PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
31 ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
32 ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
33 ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
34 ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35 **
36 ****************************************************************************/
37
38#include <AppKit/AppKit.h>
39
41#include "qcocoaintegration.h"
42#include "qcocoamenubar.h"
43#include "qcocoamenu.h"
44#include "qcocoamenuloader.h"
45#include "qcocoamenuitem.h"
46#include "qcocoansmenu.h"
47#include "qcocoahelpers.h"
48
49#if QT_CONFIG(sessionmanager)
50# include "qcocoasessionmanager.h"
51#endif
52
53#include <qevent.h>
54#include <qurl.h>
55#include <qdebug.h>
56#include <qguiapplication.h>
57#include <qpa/qwindowsysteminterface.h>
58#include <qwindowdefs.h>
59
61
63 NSObject <NSApplicationDelegate> *reflectionDelegate;
65}
66
67+ (instancetype)sharedDelegate
68{
69 static QCocoaApplicationDelegate *shared = nil;
70 static dispatch_once_t onceToken;
71 dispatch_once(&onceToken, ^{
72 shared = [[self alloc] init];
73 atexit_b(^{
74 [shared release];
75 shared = nil;
76 });
77 });
78 return shared;
79}
80
81- (instancetype)init
82{
83 self = [super init];
84 if (self) {
85 inLaunch = true;
86 }
87 return self;
88}
89
90- (void)dealloc
91{
92 [_dockMenu release];
93 if (reflectionDelegate) {
94 [[NSApplication sharedApplication] setDelegate:reflectionDelegate];
95 [reflectionDelegate release];
96 }
97 [[NSNotificationCenter defaultCenter] removeObserver:self];
98
99 [super dealloc];
100}
101
102- (NSMenu *)applicationDockMenu:(NSApplication *)sender
103{
104 Q_UNUSED(sender);
105 // Manually invoke the delegate's -menuWillOpen: method.
106 // See QTBUG-39604 (and its fix) for details.
107 [self.dockMenu.delegate menuWillOpen:self.dockMenu];
108 return [[self.dockMenu retain] autorelease];
109}
110
111// This function will only be called when NSApp is actually running.
112- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
113{
114 if ([reflectionDelegate respondsToSelector:_cmd])
115 return [reflectionDelegate applicationShouldTerminate:sender];
116
117 if (QGuiApplicationPrivate::instance()->threadData.loadRelaxed()->eventLoops.isEmpty()) {
118 // No event loop is executing. This probably means that Qt is used as a plugin,
119 // or as a part of a native Cocoa application. In any case it should be fine to
120 // terminate now.
121 qCDebug(lcQpaApplication) << "No running event loops, terminating now";
122 return NSTerminateNow;
123 }
124
125#if QT_CONFIG(sessionmanager)
127 cocoaSessionManager->resetCancellation();
128 cocoaSessionManager->appCommitData();
129
130 if (cocoaSessionManager->wasCanceled()) {
131 qCDebug(lcQpaApplication) << "Session management canceled application termination";
132 return NSTerminateCancel;
133 }
134#endif
135
136 if (!QWindowSystemInterface::handleApplicationTermination<QWindowSystemInterface::SynchronousDelivery>()) {
137 qCDebug(lcQpaApplication) << "Application termination canceled";
138 return NSTerminateCancel;
139 }
140
141 // Even if the application termination was accepted by the application we can't
142 // return NSTerminateNow, as that would trigger AppKit to ultimately call exit().
143 // We need to ensure that the runloop continues spinning so that we can return
144 // from our own event loop back to main(), and exit from there.
145 qCDebug(lcQpaApplication) << "Termination accepted, but returning to runloop for exit through main()";
146 return NSTerminateCancel;
147}
148
149- (void)applicationWillFinishLaunching:(NSNotification *)notification
150{
151 Q_UNUSED(notification);
152
153 /*
154 From the Cocoa documentation: "A good place to install event handlers
155 is in the applicationWillFinishLaunching: method of the application
156 delegate. At that point, the Application Kit has installed its default
157 event handlers, so if you install a handler for one of the same events,
158 it will replace the Application Kit version."
159 */
160
161 /*
162 If Qt is used as a plugin, we let the 3rd party application handle
163 events like quit and open file events. Otherwise, if we install our own
164 handlers, we easily end up breaking functionality the 3rd party
165 application depends on.
166 */
167 NSAppleEventManager *eventManager = [NSAppleEventManager sharedAppleEventManager];
168 [eventManager setEventHandler:self
169 andSelector:@selector(getUrl:withReplyEvent:)
170 forEventClass:kInternetEventClass
171 andEventID:kAEGetURL];
172}
173
174// called by QCocoaIntegration's destructor before resetting the application delegate to nil
175- (void)removeAppleEventHandlers
176{
177 NSAppleEventManager *eventManager = [NSAppleEventManager sharedAppleEventManager];
178 [eventManager removeEventHandlerForEventClass:kInternetEventClass andEventID:kAEGetURL];
179}
180
181- (bool)inLaunch
182{
183 return inLaunch;
184}
185
186- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
187{
188 Q_UNUSED(aNotification);
189 inLaunch = false;
190
191 if (qEnvironmentVariableIsEmpty("QT_MAC_DISABLE_FOREGROUND_APPLICATION_TRANSFORM")) {
192 // Move the application window to front to avoid launching behind the terminal.
193 // Ignoring other apps is necessary (we must ignore the terminal), but makes
194 // Qt apps play slightly less nice with other apps when lanching from Finder
195 // (See the activateIgnoringOtherApps docs.)
196 [[NSApplication sharedApplication] activateIgnoringOtherApps:YES];
197 }
198
200}
201
202- (void)application:(NSApplication *)sender openFiles:(NSArray *)filenames
203{
204 Q_UNUSED(filenames);
205 Q_UNUSED(sender);
206
207 for (NSString *fileName in filenames) {
208 QString qtFileName = QString::fromNSString(fileName);
209 if (inLaunch) {
210 // We need to be careful because Cocoa will be nice enough to take
211 // command line arguments and send them to us as events. Given the history
212 // of Qt Applications, this will result in behavior people don't want, as
213 // they might be doing the opening themselves with the command line parsing.
214 if (qApp->arguments().contains(qtFileName))
215 continue;
216 }
218 }
219
220 if ([reflectionDelegate respondsToSelector:_cmd])
221 [reflectionDelegate application:sender openFiles:filenames];
222
223}
224
225- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender
226{
227 if ([reflectionDelegate respondsToSelector:_cmd])
228 return [reflectionDelegate applicationShouldTerminateAfterLastWindowClosed:sender];
229
230 return NO; // Someday qApp->quitOnLastWindowClosed(); when QApp and NSApp work closer together.
231}
232
233- (void)applicationDidBecomeActive:(NSNotification *)notification
234{
236 [[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver:QCocoaWindow::s_applicationActivationObserver];
238 }
239
240 if ([reflectionDelegate respondsToSelector:_cmd])
241 [reflectionDelegate applicationDidBecomeActive:notification];
242
244
246 QPointF windowPoint;
247 QPointF screenPoint;
249 [view convertFromScreen:[NSEvent mouseLocation] toWindowPoint:&windowPoint andScreenPoint:&screenPoint];
250 QWindow *windowUnderMouse = QCocoaWindow::s_windowUnderMouse->window();
251 qCInfo(lcQpaMouse) << "Application activated with mouse at" << windowPoint << "; sending" << QEvent::Enter << "to" << windowUnderMouse;
252 QWindowSystemInterface::handleEnterEvent(windowUnderMouse, windowPoint, screenPoint);
253 }
254}
255
256- (void)applicationDidResignActive:(NSNotification *)notification
257{
258 if ([reflectionDelegate respondsToSelector:_cmd])
259 [reflectionDelegate applicationDidResignActive:notification];
260
262
264 QWindow *windowUnderMouse = QCocoaWindow::s_windowUnderMouse->window();
265 qCInfo(lcQpaMouse) << "Application deactivated; sending" << QEvent::Leave << "to" << windowUnderMouse;
267 }
268}
269
270- (BOOL)applicationShouldHandleReopen:(NSApplication *)theApplication hasVisibleWindows:(BOOL)flag
271{
272 if ([reflectionDelegate respondsToSelector:_cmd])
273 return [reflectionDelegate applicationShouldHandleReopen:theApplication hasVisibleWindows:flag];
274
275 /*
276 true to force delivery of the event even if the application state is already active,
277 because rapp (handle reopen) events are sent each time the dock icon is clicked regardless
278 of the active state of the application or number of visible windows. For example, a browser
279 app that has no windows opened would need the event be to delivered even if it was already
280 active in order to create a new window as per OS X conventions.
281 */
283
284 return YES;
285}
286
287- (void)setReflectionDelegate:(NSObject <NSApplicationDelegate> *)oldDelegate
288{
289 [oldDelegate retain];
290 [reflectionDelegate release];
291 reflectionDelegate = oldDelegate;
292}
293
294- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
295{
296 NSMethodSignature *result = [super methodSignatureForSelector:aSelector];
297 if (!result && reflectionDelegate) {
298 result = [reflectionDelegate methodSignatureForSelector:aSelector];
299 }
300 return result;
301}
302
303- (BOOL)respondsToSelector:(SEL)aSelector
304{
305 return [super respondsToSelector:aSelector] || [reflectionDelegate respondsToSelector:aSelector];
306}
307
308- (void)forwardInvocation:(NSInvocation *)invocation
309{
310 SEL invocationSelector = [invocation selector];
311 if ([reflectionDelegate respondsToSelector:invocationSelector])
312 [invocation invokeWithTarget:reflectionDelegate];
313 else
314 [self doesNotRecognizeSelector:invocationSelector];
315}
316
317- (void)getUrl:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent
318{
319 Q_UNUSED(replyEvent);
320 NSString *urlString = [[event paramDescriptorForKeyword:keyDirectObject] stringValue];
321 // The string we get from the requesting application might not necessarily meet
322 // QUrl's requirement for a IDN-compliant host. So if we can't parse into a QUrl,
323 // then we pass the string on to the application as the name of a file (and
324 // QFileOpenEvent::file is not guaranteed to be the path to a local, open'able
325 // file anyway).
326 const QString qurlString = QString::fromNSString(urlString);
327 if (const QUrl url(qurlString); url.isValid())
329 else
331}
332@end
333
334@implementation QCocoaApplicationDelegate (Menus)
335
336- (BOOL)validateMenuItem:(NSMenuItem*)item
337{
338 qCDebug(lcQpaMenus) << "Validating" << item << "for" << self;
339
340 auto *nativeItem = qt_objc_cast<QCocoaNSMenuItem *>(item);
341 if (!nativeItem)
342 return item.enabled; // FIXME Test with with Qt as plugin or embedded QWindow.
343
344 auto *platformItem = nativeItem.platformMenuItem;
345 if (!platformItem) // Try a bit harder with orphan menu items
346 return item.hasSubmenu || (item.enabled && (item.action != @selector(qt_itemFired:)));
347
348 // Menu-holding items are always enabled, as it's conventional in Cocoa
349 if (platformItem->menu())
350 return YES;
351
352 return platformItem->isEnabled();
353}
354
355@end
356
357@implementation QCocoaApplicationDelegate (MenuAPI)
358
359- (void)qt_itemFired:(QCocoaNSMenuItem *)item
360{
361 qCDebug(lcQpaMenus) << "Activating" << item;
362
363 if (item.hasSubmenu)
364 return;
365
366 auto *nativeItem = qt_objc_cast<QCocoaNSMenuItem *>(item);
367 Q_ASSERT_X(nativeItem, qPrintable(__FUNCTION__), "Triggered menu item is not a QCocoaNSMenuItem.");
368 auto *platformItem = nativeItem.platformMenuItem;
369 // Menu-holding items also get a target to play nicely
370 // with NSMenuValidation but should not trigger.
371 if (!platformItem || platformItem->menu())
372 return;
373
374 QScopedScopeLevelCounter scopeLevelCounter(QGuiApplicationPrivate::instance()->threadData.loadRelaxed());
375 QGuiApplicationPrivate::modifier_buttons = QAppleKeyMapper::fromCocoaModifiers([NSEvent modifierFlags]);
376
378 activatedSignal.invoke(platformItem, Qt::QueuedConnection);
379}
380
381@end
static void insertWindowMenu()
static QCocoaSessionManager * instance()
static QPointer< QCocoaWindow > s_windowUnderMouse
static id s_applicationActivationObserver
bool isEnabled() const
Returns true if the item is enabled; otherwise, false is returned.
static Qt::KeyboardModifiers modifier_buttons
static QGuiApplicationPrivate * instance()
\inmodule QtCore
Definition qmetaobject.h:18
static QMetaMethod fromSignal(PointerToMemberFunction signal)
bool invoke(QObject *object, Qt::ConnectionType connectionType, QGenericReturnArgument returnValue, QGenericArgument val0=QGenericArgument(nullptr), QGenericArgument val1=QGenericArgument(), QGenericArgument val2=QGenericArgument(), QGenericArgument val3=QGenericArgument(), QGenericArgument val4=QGenericArgument(), QGenericArgument val5=QGenericArgument(), QGenericArgument val6=QGenericArgument(), QGenericArgument val7=QGenericArgument(), QGenericArgument val8=QGenericArgument(), QGenericArgument val9=QGenericArgument()) const
\obsolete [6.5] Please use the variadic overload of this function
QWindow * window() const
Returns the window which belongs to the QPlatformWindow.
\inmodule QtCore\reentrant
Definition qpoint.h:214
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:127
\inmodule QtCore
Definition qurl.h:94
bool isValid() const
Returns true if the URL is non-empty and valid; otherwise returns false.
Definition qurl.cpp:1874
static void handleLeaveEvent(QWindow *window)
static void handleFileOpenEvent(const QString &fileName)
static void handleApplicationStateChanged(Qt::ApplicationState newState, bool forcePropagate=false)
static void handleEnterEvent(QWindow *window, const QPointF &local=QPointF(), const QPointF &global=QPointF())
\inmodule QtGui
Definition qwindow.h:63
void openFiles(const std::string &accept, FileSelectMode fileSelectMode, const std::function< void(int fileCount)> &fileDialogClosed, const std::function< char *(uint64_t size, const std::string &name)> &acceptFile, const std::function< void()> &fileDataReady)
@ ApplicationActive
Definition qnamespace.h:265
@ ApplicationInactive
Definition qnamespace.h:264
@ QueuedConnection
QString self
Definition language.cpp:57
QNSView * qnsview_cast(NSView *view)
Returns the view cast to a QNSview if possible.
#define qApp
DBusConnection const char DBusError DBusBusType DBusError return DBusConnection DBusHandleMessageFunction void DBusFreeFunction return DBusConnection return DBusConnection return const char DBusError return DBusConnection DBusMessage dbus_uint32_t return DBusConnection dbus_bool_t DBusConnection DBusAddWatchFunction DBusRemoveWatchFunction DBusWatchToggledFunction void DBusFreeFunction return DBusConnection DBusDispatchStatusFunction void DBusFreeFunction DBusTimeout return DBusTimeout return DBusWatch return DBusWatch unsigned int return DBusError const DBusError return const DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessageIter int const void return DBusMessageIter DBusMessageIter return DBusMessageIter void DBusMessageIter void int return DBusMessage DBusMessageIter return DBusMessageIter return DBusMessageIter DBusMessageIter const char const char const char const char return DBusMessage return DBusMessage const char return DBusMessage dbus_bool_t return DBusMessage dbus_uint32_t return DBusMessage void
#define qCInfo(category,...)
#define qCDebug(category,...)
GLuint in
GLuint64EXT * result
[6]
#define Q_ASSERT_X(cond, x, msg)
Definition qrandom.cpp:48
#define qPrintable(string)
Definition qstring.h:1391
Q_CORE_EXPORT bool qEnvironmentVariableIsEmpty(const char *varName) noexcept
static QT_BEGIN_NAMESPACE void init(QTextBoundaryFinder::BoundaryType type, QStringView str, QCharAttributes *attributes)
#define Q_UNUSED(x)
QUrl url("example.com")
[constructor-url-reference]
QGraphicsItem * item
QQuickView * view
[0]