Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
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 if ([reflectionDelegate respondsToSelector:_cmd])
152 [reflectionDelegate applicationWillFinishLaunching:notification];
153
154 /*
155 From the Cocoa documentation: "A good place to install event handlers
156 is in the applicationWillFinishLaunching: method of the application
157 delegate. At that point, the Application Kit has installed its default
158 event handlers, so if you install a handler for one of the same events,
159 it will replace the Application Kit version."
160 */
161
162 /*
163 If Qt is used as a plugin, we let the 3rd party application handle
164 events like quit and open file events. Otherwise, if we install our own
165 handlers, we easily end up breaking functionality the 3rd party
166 application depends on.
167 */
168 NSAppleEventManager *eventManager = [NSAppleEventManager sharedAppleEventManager];
169 [eventManager setEventHandler:self
170 andSelector:@selector(getUrl:withReplyEvent:)
171 forEventClass:kInternetEventClass
172 andEventID:kAEGetURL];
173}
174
175// called by QCocoaIntegration's destructor before resetting the application delegate to nil
176- (void)removeAppleEventHandlers
177{
178 NSAppleEventManager *eventManager = [NSAppleEventManager sharedAppleEventManager];
179 [eventManager removeEventHandlerForEventClass:kInternetEventClass andEventID:kAEGetURL];
180}
181
182- (bool)inLaunch
183{
184 return inLaunch;
185}
186
187- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
188{
189 if ([reflectionDelegate respondsToSelector:_cmd])
190 [reflectionDelegate applicationDidFinishLaunching:aNotification];
191
192 inLaunch = false;
193
194 if (qEnvironmentVariableIsEmpty("QT_MAC_DISABLE_FOREGROUND_APPLICATION_TRANSFORM")) {
195 auto frontmostApplication = NSWorkspace.sharedWorkspace.frontmostApplication;
196 auto currentApplication = NSRunningApplication.currentApplication;
197 if (frontmostApplication != currentApplication) {
198 // Move the application to front to avoid launching behind the terminal.
199 // Ignoring other apps is necessary (we must ignore the terminal), but makes
200 // Qt apps play slightly less nice with other apps when launching from Finder
201 // (see the activateIgnoringOtherApps docs). FIXME: Try to distinguish between
202 // being non-active here because another application stole activation in the
203 // time it took us to launch from Finder, and being non-active because we were
204 // launched from Terminal or something that doesn't activate us at all.
205 qCDebug(lcQpaApplication) << "Launched with" << frontmostApplication
206 << "as frontmost application. Activating" << currentApplication << "instead.";
207 [NSApplication.sharedApplication activateIgnoringOtherApps:YES];
208 }
209
210 // Qt windows are typically shown in main(), at which point the application
211 // is not active yet. When the application is activated, either externally
212 // or via the override above, it will only bring the main and key windows
213 // forward, which differs from the behavior if these windows had been shown
214 // once the application was already active. To work around this, we explicitly
215 // activate the current application again, bringing all windows to the front.
216 [currentApplication activateWithOptions:NSApplicationActivateAllWindows];
217 }
218
220}
221
222- (void)application:(NSApplication *)sender openFiles:(NSArray *)filenames
223{
224 Q_UNUSED(filenames);
225 Q_UNUSED(sender);
226
227 for (NSString *fileName in filenames) {
228 QString qtFileName = QString::fromNSString(fileName);
229 if (inLaunch) {
230 // We need to be careful because Cocoa will be nice enough to take
231 // command line arguments and send them to us as events. Given the history
232 // of Qt Applications, this will result in behavior people don't want, as
233 // they might be doing the opening themselves with the command line parsing.
234 if (qApp->arguments().contains(qtFileName))
235 continue;
236 }
238 }
239
240 if ([reflectionDelegate respondsToSelector:_cmd])
241 [reflectionDelegate application:sender openFiles:filenames];
242
243}
244
245- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender
246{
247 if ([reflectionDelegate respondsToSelector:_cmd])
248 return [reflectionDelegate applicationShouldTerminateAfterLastWindowClosed:sender];
249
250 return NO; // Someday qApp->quitOnLastWindowClosed(); when QApp and NSApp work closer together.
251}
252
253- (void)applicationDidBecomeActive:(NSNotification *)notification
254{
256 [[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver:QCocoaWindow::s_applicationActivationObserver];
258 }
259
260 if ([reflectionDelegate respondsToSelector:_cmd])
261 [reflectionDelegate applicationDidBecomeActive:notification];
262
264
266 QPointF windowPoint;
267 QPointF screenPoint;
269 [view convertFromScreen:[NSEvent mouseLocation] toWindowPoint:&windowPoint andScreenPoint:&screenPoint];
270 QWindow *windowUnderMouse = QCocoaWindow::s_windowUnderMouse->window();
271 qCInfo(lcQpaMouse) << "Application activated with mouse at" << windowPoint << "; sending" << QEvent::Enter << "to" << windowUnderMouse;
272 QWindowSystemInterface::handleEnterEvent(windowUnderMouse, windowPoint, screenPoint);
273 }
274}
275
276- (void)applicationDidResignActive:(NSNotification *)notification
277{
278 if ([reflectionDelegate respondsToSelector:_cmd])
279 [reflectionDelegate applicationDidResignActive:notification];
280
282
284 QWindow *windowUnderMouse = QCocoaWindow::s_windowUnderMouse->window();
285 qCInfo(lcQpaMouse) << "Application deactivated; sending" << QEvent::Leave << "to" << windowUnderMouse;
287 }
288}
289
290- (BOOL)applicationShouldHandleReopen:(NSApplication *)theApplication hasVisibleWindows:(BOOL)flag
291{
292 if ([reflectionDelegate respondsToSelector:_cmd])
293 return [reflectionDelegate applicationShouldHandleReopen:theApplication hasVisibleWindows:flag];
294
295 /*
296 true to force delivery of the event even if the application state is already active,
297 because rapp (handle reopen) events are sent each time the dock icon is clicked regardless
298 of the active state of the application or number of visible windows. For example, a browser
299 app that has no windows opened would need the event be to delivered even if it was already
300 active in order to create a new window as per OS X conventions.
301 */
303
304 return YES;
305}
306
307- (void)setReflectionDelegate:(NSObject <NSApplicationDelegate> *)oldDelegate
308{
309 [oldDelegate retain];
310 [reflectionDelegate release];
311 reflectionDelegate = oldDelegate;
312}
313
314- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
315{
316 NSMethodSignature *result = [super methodSignatureForSelector:aSelector];
317 if (!result && reflectionDelegate) {
318 result = [reflectionDelegate methodSignatureForSelector:aSelector];
319 }
320 return result;
321}
322
323- (BOOL)respondsToSelector:(SEL)aSelector
324{
325 return [super respondsToSelector:aSelector] || [reflectionDelegate respondsToSelector:aSelector];
326}
327
328- (void)forwardInvocation:(NSInvocation *)invocation
329{
330 SEL invocationSelector = [invocation selector];
331 if ([reflectionDelegate respondsToSelector:invocationSelector])
332 [invocation invokeWithTarget:reflectionDelegate];
333 else
334 [self doesNotRecognizeSelector:invocationSelector];
335}
336
337- (BOOL)application:(NSApplication *)application continueUserActivity:(NSUserActivity *)userActivity
338 restorationHandler:(void(^)(NSArray<id<NSUserActivityRestoring>> *restorableObjects))restorationHandler
339{
340 // Check if eg. user has installed an app delegate capable of handling this
341 if ([reflectionDelegate respondsToSelector:_cmd]
342 && [reflectionDelegate application:application continueUserActivity:userActivity
343 restorationHandler:restorationHandler] == YES) {
344 return YES;
345 }
346
348 return NO;
349
350 if ([userActivity.activityType isEqualToString:NSUserActivityTypeBrowsingWeb]) {
352 Q_ASSERT(cocoaIntegration);
353 return cocoaIntegration->services()->handleUrl(QUrl::fromNSURL(userActivity.webpageURL));
354 }
355
356 return NO;
357}
358
359- (void)getUrl:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent
360{
361 Q_UNUSED(replyEvent);
362
363 NSString *urlString = [[event paramDescriptorForKeyword:keyDirectObject] stringValue];
364 const QString qurlString = QString::fromNSString(urlString);
365
366 if (event.eventClass == kInternetEventClass && event.eventID == kAEGetURL) {
367 // 'GURL' (Get URL) event this application should handle
369 return;
371 Q_ASSERT(cocoaIntegration);
372 cocoaIntegration->services()->handleUrl(QUrl(qurlString));
373 return;
374 }
375
376 // The string we get from the requesting application might not necessarily meet
377 // QUrl's requirement for a IDN-compliant host. So if we can't parse into a QUrl,
378 // then we pass the string on to the application as the name of a file (and
379 // QFileOpenEvent::file is not guaranteed to be the path to a local, open'able
380 // file anyway).
381 if (const QUrl url(qurlString); url.isValid())
383 else
385}
386
387- (BOOL)applicationSupportsSecureRestorableState:(NSApplication *)application
388{
389 if (@available(macOS 12, *)) {
390 if ([reflectionDelegate respondsToSelector:_cmd])
391 return [reflectionDelegate applicationSupportsSecureRestorableState:application];
392 }
393
394 // We don't support or implement state restorations via the AppKit
395 // state restoration APIs, but if we did, we would/should support
396 // secure state restoration. This is the default for apps linked
397 // against the macOS 14 SDK, but as we target versions below that
398 // as well we need to return YES here explicitly to silence a runtime
399 // warning.
400 return YES;
401}
402
403@end
404
405@implementation QCocoaApplicationDelegate (Menus)
406
407- (BOOL)validateMenuItem:(NSMenuItem*)item
408{
409 qCDebug(lcQpaMenus) << "Validating" << item << "for" << self;
410
411 auto *nativeItem = qt_objc_cast<QCocoaNSMenuItem *>(item);
412 if (!nativeItem)
413 return item.enabled; // FIXME Test with with Qt as plugin or embedded QWindow.
414
415 auto *platformItem = nativeItem.platformMenuItem;
416 if (!platformItem) // Try a bit harder with orphan menu items
417 return item.hasSubmenu || (item.enabled && (item.action != @selector(qt_itemFired:)));
418
419 // Menu-holding items are always enabled, as it's conventional in Cocoa
420 if (platformItem->menu())
421 return YES;
422
423 return platformItem->isEnabled();
424}
425
426@end
427
428@implementation QCocoaApplicationDelegate (MenuAPI)
429
430- (void)qt_itemFired:(QCocoaNSMenuItem *)item
431{
432 qCDebug(lcQpaMenus) << "Activating" << item;
433
434 if (item.hasSubmenu)
435 return;
436
437 auto *nativeItem = qt_objc_cast<QCocoaNSMenuItem *>(item);
438 Q_ASSERT_X(nativeItem, qPrintable(__FUNCTION__), "Triggered menu item is not a QCocoaNSMenuItem.");
439 auto *platformItem = nativeItem.platformMenuItem;
440 // Menu-holding items also get a target to play nicely
441 // with NSMenuValidation but should not trigger.
442 if (!platformItem || platformItem->menu())
443 return;
444
445 QGuiApplicationPrivate::modifier_buttons = QAppleKeyMapper::fromCocoaModifiers([NSEvent modifierFlags]);
446
448 activatedSignal.invoke(platformItem, Qt::QueuedConnection);
449}
450
451@end
static QCocoaIntegration * instance()
static void insertWindowMenu()
static QCocoaSessionManager * instance()
static QPointer< QCocoaWindow > s_windowUnderMouse
static id s_applicationActivationObserver
static QCoreApplication * instance() noexcept
Returns a pointer to the application's QCoreApplication (or QGuiApplication/QApplication) instance.
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:19
static QMetaMethod fromSignal(PointerToMemberFunction signal)
\inmodule QtCore\reentrant
Definition qpoint.h:217
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
\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:1882
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:266
@ ApplicationInactive
Definition qnamespace.h:265
@ QueuedConnection
QString self
Definition language.cpp:58
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,...)
struct _cl_event * event
GLuint in
GLuint64EXT * result
[6]
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
#define Q_ASSERT_X(cond, x, msg)
Definition qrandom.cpp:48
#define qPrintable(string)
Definition qstring.h:1531
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]