1/**************************************************************************** 2** 3** Copyright (C) 2018 The Qt Company Ltd. 4** Contact: https://www.qt.io/licensing/ 5** 6** This file is part of the plugins of the Qt Toolkit. 7** 8** $QT_BEGIN_LICENSE:LGPL$ 9** Commercial License Usage 10** Licensees holding valid commercial Qt licenses may use this file in 11** accordance with the commercial license agreement provided with the 12** Software or, alternatively, in accordance with the terms contained in 13** a written agreement between you and The Qt Company. For licensing terms 14** and conditions see https://www.qt.io/terms-conditions. For further 15** information use the contact form at https://www.qt.io/contact-us. 16** 17** GNU Lesser General Public License Usage 18** Alternatively, this file may be used under the terms of the GNU Lesser 19** General Public License version 3 as published by the Free Software 20** Foundation and appearing in the file LICENSE.LGPL3 included in the 21** packaging of this file. Please review the following information to 22** ensure the GNU Lesser General Public License version 3 requirements 23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. 24** 25** GNU General Public License Usage 26** Alternatively, this file may be used under the terms of the GNU 27** General Public License version 2.0 or (at your option) the GNU General 28** Public license version 3 or any later version approved by the KDE Free 29** Qt Foundation. The licenses are as published by the Free Software 30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 31** included in the packaging of this file. Please review the following 32** information to ensure the GNU General Public License requirements will 33** be met: https://www.gnu.org/licenses/gpl-2.0.html and 34** https://www.gnu.org/licenses/gpl-3.0.html. 35** 36** $QT_END_LICENSE$ 37** 38****************************************************************************/ 39 40/**************************************************************************** 41 ** 42 ** Copyright (c) 2007-2008, Apple, Inc. 43 ** 44 ** All rights reserved. 45 ** 46 ** Redistribution and use in source and binary forms, with or without 47 ** modification, are permitted provided that the following conditions are met: 48 ** 49 ** * Redistributions of source code must retain the above copyright notice, 50 ** this list of conditions and the following disclaimer. 51 ** 52 ** * Redistributions in binary form must reproduce the above copyright notice, 53 ** this list of conditions and the following disclaimer in the documentation 54 ** and/or other materials provided with the distribution. 55 ** 56 ** * Neither the name of Apple, Inc. nor the names of its contributors 57 ** may be used to endorse or promote products derived from this software 58 ** without specific prior written permission. 59 ** 60 ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 61 ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 62 ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 63 ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 64 ** CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 65 ** EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 66 ** PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 67 ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 68 ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 69 ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 70 ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 71 ** 72 ****************************************************************************/ 73 74 75#import "qcocoaapplicationdelegate.h" 76#include "qcocoaintegration.h" 77#include "qcocoamenu.h" 78#include "qcocoamenuloader.h" 79#include "qcocoamenuitem.h" 80#include "qcocoansmenu.h" 81 82#if QT_CONFIG(sessionmanager) 83# include "qcocoasessionmanager.h" 84#endif 85 86#include <qevent.h> 87#include <qurl.h> 88#include <qdebug.h> 89#include <qguiapplication.h> 90#include <qpa/qwindowsysteminterface.h> 91#include <qwindowdefs.h> 92 93QT_BEGIN_NAMESPACE 94Q_LOGGING_CATEGORY(lcQpaApplication, "qt.qpa.application"); 95QT_END_NAMESPACE 96 97QT_USE_NAMESPACE 98 99@implementation QCocoaApplicationDelegate { 100 NSObject <NSApplicationDelegate> *reflectionDelegate; 101 bool inLaunch; 102} 103 104+ (instancetype)sharedDelegate 105{ 106 static QCocoaApplicationDelegate *shared = nil; 107 static dispatch_once_t onceToken; 108 dispatch_once(&onceToken, ^{ 109 shared = [[self alloc] init]; 110 atexit_b(^{ 111 [shared release]; 112 shared = nil; 113 }); 114 }); 115 return shared; 116} 117 118- (instancetype)init 119{ 120 self = [super init]; 121 if (self) { 122 inLaunch = true; 123 } 124 return self; 125} 126 127- (void)dealloc 128{ 129 [_dockMenu release]; 130 if (reflectionDelegate) { 131 [[NSApplication sharedApplication] setDelegate:reflectionDelegate]; 132 [reflectionDelegate release]; 133 } 134 [[NSNotificationCenter defaultCenter] removeObserver:self]; 135 136 [super dealloc]; 137} 138 139- (NSMenu *)applicationDockMenu:(NSApplication *)sender 140{ 141 Q_UNUSED(sender); 142 // Manually invoke the delegate's -menuWillOpen: method. 143 // See QTBUG-39604 (and its fix) for details. 144 [self.dockMenu.delegate menuWillOpen:self.dockMenu]; 145 return [[self.dockMenu retain] autorelease]; 146} 147 148// This function will only be called when NSApp is actually running. 149- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender 150{ 151 if ([reflectionDelegate respondsToSelector:_cmd]) 152 return [reflectionDelegate applicationShouldTerminate:sender]; 153 154 if (QGuiApplicationPrivate::instance()->threadData.loadRelaxed()->eventLoops.isEmpty()) { 155 // No event loop is executing. This probably means that Qt is used as a plugin, 156 // or as a part of a native Cocoa application. In any case it should be fine to 157 // terminate now. 158 qCDebug(lcQpaApplication) << "No running event loops, terminating now"; 159 return NSTerminateNow; 160 } 161 162#if QT_CONFIG(sessionmanager) 163 QCocoaSessionManager *cocoaSessionManager = QCocoaSessionManager::instance(); 164 cocoaSessionManager->resetCancellation(); 165 cocoaSessionManager->appCommitData(); 166 167 if (cocoaSessionManager->wasCanceled()) { 168 qCDebug(lcQpaApplication) << "Session management canceled application termination"; 169 return NSTerminateCancel; 170 } 171#endif 172 173 if (!QWindowSystemInterface::handleApplicationTermination<QWindowSystemInterface::SynchronousDelivery>()) { 174 qCDebug(lcQpaApplication) << "Application termination canceled"; 175 return NSTerminateCancel; 176 } 177 178 // Even if the application termination was accepted by the application we can't 179 // return NSTerminateNow, as that would trigger AppKit to ultimately call exit(). 180 // We need to ensure that the runloop continues spinning so that we can return 181 // from our own event loop back to main(), and exit from there. 182 qCDebug(lcQpaApplication) << "Termination accepted, but returning to runloop for exit through main()"; 183 return NSTerminateCancel; 184} 185 186- (void)applicationWillFinishLaunching:(NSNotification *)notification 187{ 188 Q_UNUSED(notification); 189 190 /* 191 From the Cocoa documentation: "A good place to install event handlers 192 is in the applicationWillFinishLaunching: method of the application 193 delegate. At that point, the Application Kit has installed its default 194 event handlers, so if you install a handler for one of the same events, 195 it will replace the Application Kit version." 196 */ 197 198 /* 199 If Qt is used as a plugin, we let the 3rd party application handle 200 events like quit and open file events. Otherwise, if we install our own 201 handlers, we easily end up breaking functionality the 3rd party 202 application depends on. 203 */ 204 NSAppleEventManager *eventManager = [NSAppleEventManager sharedAppleEventManager]; 205 [eventManager setEventHandler:self 206 andSelector:@selector(getUrl:withReplyEvent:) 207 forEventClass:kInternetEventClass 208 andEventID:kAEGetURL]; 209} 210 211// called by QCocoaIntegration's destructor before resetting the application delegate to nil 212- (void)removeAppleEventHandlers 213{ 214 NSAppleEventManager *eventManager = [NSAppleEventManager sharedAppleEventManager]; 215 [eventManager removeEventHandlerForEventClass:kInternetEventClass andEventID:kAEGetURL]; 216} 217 218- (bool)inLaunch 219{ 220 return inLaunch; 221} 222 223- (void)applicationDidFinishLaunching:(NSNotification *)aNotification 224{ 225 Q_UNUSED(aNotification); 226 inLaunch = false; 227 228 if (qEnvironmentVariableIsEmpty("QT_MAC_DISABLE_FOREGROUND_APPLICATION_TRANSFORM")) { 229 // Move the application window to front to avoid launching behind the terminal. 230 // Ignoring other apps is necessary (we must ignore the terminal), but makes 231 // Qt apps play slightly less nice with other apps when lanching from Finder 232 // (See the activateIgnoringOtherApps docs.) 233 [[NSApplication sharedApplication] activateIgnoringOtherApps:YES]; 234 } 235} 236 237- (void)application:(NSApplication *)sender openFiles:(NSArray *)filenames 238{ 239 Q_UNUSED(filenames); 240 Q_UNUSED(sender); 241 242 for (NSString *fileName in filenames) { 243 QString qtFileName = QString::fromNSString(fileName); 244 if (inLaunch) { 245 // We need to be careful because Cocoa will be nice enough to take 246 // command line arguments and send them to us as events. Given the history 247 // of Qt Applications, this will result in behavior people don't want, as 248 // they might be doing the opening themselves with the command line parsing. 249 if (qApp->arguments().contains(qtFileName)) 250 continue; 251 } 252 QWindowSystemInterface::handleFileOpenEvent(qtFileName); 253 } 254 255 if ([reflectionDelegate respondsToSelector:_cmd]) 256 [reflectionDelegate application:sender openFiles:filenames]; 257 258} 259 260- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender 261{ 262 if ([reflectionDelegate respondsToSelector:_cmd]) 263 return [reflectionDelegate applicationShouldTerminateAfterLastWindowClosed:sender]; 264 265 return NO; // Someday qApp->quitOnLastWindowClosed(); when QApp and NSApp work closer together. 266} 267 268- (void)applicationDidBecomeActive:(NSNotification *)notification 269{ 270 if ([reflectionDelegate respondsToSelector:_cmd]) 271 [reflectionDelegate applicationDidBecomeActive:notification]; 272 273 QWindowSystemInterface::handleApplicationStateChanged(Qt::ApplicationActive); 274} 275 276- (void)applicationDidResignActive:(NSNotification *)notification 277{ 278 if ([reflectionDelegate respondsToSelector:_cmd]) 279 [reflectionDelegate applicationDidResignActive:notification]; 280 281 QWindowSystemInterface::handleApplicationStateChanged(Qt::ApplicationInactive); 282} 283 284- (BOOL)applicationShouldHandleReopen:(NSApplication *)theApplication hasVisibleWindows:(BOOL)flag 285{ 286 if ([reflectionDelegate respondsToSelector:_cmd]) 287 return [reflectionDelegate applicationShouldHandleReopen:theApplication hasVisibleWindows:flag]; 288 289 /* 290 true to force delivery of the event even if the application state is already active, 291 because rapp (handle reopen) events are sent each time the dock icon is clicked regardless 292 of the active state of the application or number of visible windows. For example, a browser 293 app that has no windows opened would need the event be to delivered even if it was already 294 active in order to create a new window as per OS X conventions. 295 */ 296 QWindowSystemInterface::handleApplicationStateChanged(Qt::ApplicationActive, true /*forcePropagate*/); 297 298 return YES; 299} 300 301- (void)setReflectionDelegate:(NSObject <NSApplicationDelegate> *)oldDelegate 302{ 303 [oldDelegate retain]; 304 [reflectionDelegate release]; 305 reflectionDelegate = oldDelegate; 306} 307 308- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector 309{ 310 NSMethodSignature *result = [super methodSignatureForSelector:aSelector]; 311 if (!result && reflectionDelegate) { 312 result = [reflectionDelegate methodSignatureForSelector:aSelector]; 313 } 314 return result; 315} 316 317- (BOOL)respondsToSelector:(SEL)aSelector 318{ 319 return [super respondsToSelector:aSelector] || [reflectionDelegate respondsToSelector:aSelector]; 320} 321 322- (void)forwardInvocation:(NSInvocation *)invocation 323{ 324 SEL invocationSelector = [invocation selector]; 325 if ([reflectionDelegate respondsToSelector:invocationSelector]) 326 [invocation invokeWithTarget:reflectionDelegate]; 327 else 328 [self doesNotRecognizeSelector:invocationSelector]; 329} 330 331- (void)getUrl:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent 332{ 333 Q_UNUSED(replyEvent); 334 NSString *urlString = [[event paramDescriptorForKeyword:keyDirectObject] stringValue]; 335 QWindowSystemInterface::handleFileOpenEvent(QUrl(QString::fromNSString(urlString))); 336} 337@end 338 339@implementation QCocoaApplicationDelegate (Menus) 340 341- (BOOL)validateMenuItem:(NSMenuItem*)item 342{ 343 auto *nativeItem = qt_objc_cast<QCocoaNSMenuItem *>(item); 344 if (!nativeItem) 345 return item.enabled; // FIXME Test with with Qt as plugin or embedded QWindow. 346 347 auto *platformItem = nativeItem.platformMenuItem; 348 if (!platformItem) // Try a bit harder with orphan menu itens 349 return item.hasSubmenu || (item.enabled && (item.action != @selector(qt_itemFired:))); 350 351 // Menu-holding items are always enabled, as it's conventional in Cocoa 352 if (platformItem->menu()) 353 return YES; 354 355 return platformItem->isEnabled(); 356} 357 358@end 359 360@implementation QCocoaApplicationDelegate (MenuAPI) 361 362- (void)qt_itemFired:(QCocoaNSMenuItem *)item 363{ 364 if (item.hasSubmenu) 365 return; 366 367 auto *nativeItem = qt_objc_cast<QCocoaNSMenuItem *>(item); 368 Q_ASSERT_X(nativeItem, qPrintable(__FUNCTION__), "Triggered menu item is not a QCocoaNSMenuItem."); 369 auto *platformItem = nativeItem.platformMenuItem; 370 // Menu-holding items also get a target to play nicely 371 // with NSMenuValidation but should not trigger. 372 if (!platformItem || platformItem->menu()) 373 return; 374 375 QScopedScopeLevelCounter scopeLevelCounter(QGuiApplicationPrivate::instance()->threadData.loadRelaxed()); 376 QGuiApplicationPrivate::modifier_buttons = [QNSView convertKeyModifiers:[NSEvent modifierFlags]]; 377 378 static QMetaMethod activatedSignal = QMetaMethod::fromSignal(&QCocoaMenuItem::activated); 379 activatedSignal.invoke(platformItem, Qt::QueuedConnection); 380} 381 382@end 383