1/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ 2/* 3 * This file is part of the LibreOffice project. 4 * 5 * This Source Code Form is subject to the terms of the Mozilla Public 6 * License, v. 2.0. If a copy of the MPL was not distributed with this 7 * file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 * 9 * This file incorporates work covered by the following license notice: 10 * 11 * Licensed to the Apache Software Foundation (ASF) under one or more 12 * contributor license agreements. See the NOTICE file distributed 13 * with this work for additional information regarding copyright 14 * ownership. The ASF licenses this file to you under the Apache 15 * License, Version 2.0 (the "License"); you may not use this file 16 * except in compliance with the License. You may obtain a copy of 17 * the License at http://www.apache.org/licenses/LICENSE-2.0 . 18 */ 19 20#include <sal/config.h> 21#include <config_features.h> 22 23#include <vector> 24 25#include <stdlib.h> 26 27#include <sal/main.h> 28#include <vcl/commandevent.hxx> 29#include <vcl/ImageTree.hxx> 30#include <vcl/svapp.hxx> 31#include <vcl/window.hxx> 32 33#include <osx/saldata.hxx> 34#include <osx/salframe.h> 35#include <osx/salframeview.h> 36#include <osx/salinst.h> 37#include <osx/vclnsapp.h> 38#include <quartz/utils.h> 39 40#include <premac.h> 41#include <objc/objc-runtime.h> 42#import "Carbon/Carbon.h" 43#import "apple_remote/RemoteControl.h" 44#include <postmac.h> 45 46 47@implementation CocoaThreadEnabler 48-(void)enableCocoaThreads:(id)param 49{ 50 // do nothing, this is just to start an NSThread and therefore put 51 // Cocoa into multithread mode 52 (void)param; 53} 54@end 55 56// If you wonder how this VCL_NSApplication stuff works, one thing you 57// might have missed is that the NSPrincipalClass property in 58// desktop/macosx/Info.plist has the value VCL_NSApplication. 59 60@implementation VCL_NSApplication 61 62-(void)applicationDidFinishLaunching:(NSNotification*)pNotification 63{ 64 (void)pNotification; 65 66SAL_WNODEPRECATED_DECLARATIONS_PUSH 67 // 'NSApplicationDefined' is deprecated: first deprecated in macOS 10.12 68 NSEvent* pEvent = [NSEvent otherEventWithType: NSApplicationDefined 69 location: NSZeroPoint 70 modifierFlags: 0 71 timestamp: [[NSProcessInfo processInfo] systemUptime] 72 windowNumber: 0 73 context: nil 74 subtype: AquaSalInstance::AppExecuteSVMain 75 data1: 0 76 data2: 0 ]; 77SAL_WNODEPRECATED_DECLARATIONS_POP 78 assert( pEvent ); 79 [NSApp postEvent: pEvent atStart: NO]; 80 81 if( [NSWindow respondsToSelector:@selector(allowsAutomaticWindowTabbing)] ) 82 { 83 [NSWindow setAllowsAutomaticWindowTabbing:NO]; 84 } 85} 86 87-(void)sendEvent:(NSEvent*)pEvent 88{ 89 NSEventType eType = [pEvent type]; 90SAL_WNODEPRECATED_DECLARATIONS_PUSH 91 // 'NSAlternateKeyMask' is deprecated: first deprecated in macOS 10.12 92 // 'NSApplicationDefined' is deprecated: first deprecated in macOS 10.12 93 // 'NSClosableWindowMask' is deprecated: first deprecated in macOS 10.12 94 // 'NSCommandKeyMask' is deprecated: first deprecated in macOS 10.12 95 // 'NSControlKeyMask' is deprecated: first deprecated in macOS 10.12 96 // 'NSKeyDown' is deprecated: first deprecated in macOS 10.12 97 // 'NSMiniaturizableWindowMask' is deprecated: first deprecated in macOS 10.12 98 // 'NSShiftKeyMask' is deprecated: first deprecated in macOS 10.12 99 if( eType == NSApplicationDefined ) 100 { 101 AquaSalInstance::handleAppDefinedEvent( pEvent ); 102 } 103 else if( eType == NSKeyDown && ([pEvent modifierFlags] & NSCommandKeyMask) != 0 ) 104 { 105 NSWindow* pKeyWin = [NSApp keyWindow]; 106 if( pKeyWin && [pKeyWin isKindOfClass: [SalFrameWindow class]] ) 107 { 108 AquaSalFrame* pFrame = [static_cast<SalFrameWindow*>(pKeyWin) getSalFrame]; 109 unsigned int nModMask = ([pEvent modifierFlags] & (NSShiftKeyMask|NSControlKeyMask|NSAlternateKeyMask|NSCommandKeyMask)); 110 /* 111 * #i98949# - Cmd-M miniaturize window, Cmd-Option-M miniaturize all windows 112 */ 113 if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"m"] ) 114 { 115 if ( nModMask == NSCommandKeyMask && ([pFrame->getNSWindow() styleMask] & NSMiniaturizableWindowMask) ) 116 { 117 [pFrame->getNSWindow() performMiniaturize: nil]; 118 return; 119 } 120 121 if ( nModMask == ( NSCommandKeyMask | NSAlternateKeyMask ) ) 122 { 123 [NSApp miniaturizeAll: nil]; 124 return; 125 } 126 } 127 128 // get information whether the event was handled; keyDown returns nothing 129 GetSalData()->maKeyEventAnswer[ pEvent ] = false; 130 bool bHandled = false; 131 132 // dispatch to view directly to avoid the key event being consumed by the menubar 133 // popup windows do not get the focus, so they don't get these either 134 // simplest would be dispatch this to the key window always if it is without parent 135 // however e.g. in document we want the menu shortcut if e.g. the stylist has focus 136 if( pFrame->mpParent && !(pFrame->mnStyle & SalFrameStyleFlags::FLOAT) ) 137 { 138 [[pKeyWin contentView] keyDown: pEvent]; 139 bHandled = GetSalData()->maKeyEventAnswer[ pEvent ]; 140 } 141 142 // see whether the main menu consumes this event 143 // if not, we want to dispatch it ourselves. Unless we do this "trick" 144 // the main menu just beeps for an unknown or disabled key equivalent 145 // and swallows the event wholesale 146 NSMenu* pMainMenu = [NSApp mainMenu]; 147 if( ! bHandled && 148 (pMainMenu == nullptr || ! [NSMenu menuBarVisible] || ! [pMainMenu performKeyEquivalent: pEvent]) ) 149 { 150 [[pKeyWin contentView] keyDown: pEvent]; 151 bHandled = GetSalData()->maKeyEventAnswer[ pEvent ]; 152 } 153 else 154 { 155 bHandled = true; // event handled already or main menu just handled it 156 } 157 GetSalData()->maKeyEventAnswer.erase( pEvent ); 158 159 if( bHandled ) 160 return; 161 } 162 else if( pKeyWin ) 163 { 164 // #i94601# a window not of vcl's making has the focus. 165 // Since our menus do not invoke the usual commands 166 // try to play nice with native windows like the file dialog 167 // and emulate them 168 // precondition: this ONLY works because CMD-V (paste), CMD-C (copy) and CMD-X (cut) are 169 // NOT localized, that is the same in all locales. Should this be 170 // different in any locale, this hack will fail. 171 unsigned int nModMask = ([pEvent modifierFlags] & (NSShiftKeyMask|NSControlKeyMask|NSAlternateKeyMask|NSCommandKeyMask)); 172 if( nModMask == NSCommandKeyMask ) 173 { 174 175 if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"v"] ) 176 { 177 if( [NSApp sendAction: @selector(paste:) to: nil from: nil] ) 178 return; 179 } 180 else if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"c"] ) 181 { 182 if( [NSApp sendAction: @selector(copy:) to: nil from: nil] ) 183 return; 184 } 185 else if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"x"] ) 186 { 187 if( [NSApp sendAction: @selector(cut:) to: nil from: nil] ) 188 return; 189 } 190 else if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"a"] ) 191 { 192 if( [NSApp sendAction: @selector(selectAll:) to: nil from: nil] ) 193 return; 194 } 195 else if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"z"] ) 196 { 197 if( [NSApp sendAction: @selector(undo:) to: nil from: nil] ) 198 return; 199 } 200 } 201 else if( nModMask == (NSCommandKeyMask|NSShiftKeyMask) ) 202 { 203 if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"Z"] ) 204 { 205 if( [NSApp sendAction: @selector(redo:) to: nil from: nil] ) 206 return; 207 } 208 } 209 } 210 } 211SAL_WNODEPRECATED_DECLARATIONS_POP 212 [super sendEvent: pEvent]; 213} 214 215-(void)sendSuperEvent:(NSEvent*)pEvent 216{ 217 [super sendEvent: pEvent]; 218} 219 220-(NSMenu*)applicationDockMenu:(NSApplication *)sender 221{ 222 (void)sender; 223 return AquaSalInstance::GetDynamicDockMenu(); 224} 225 226-(BOOL)application: (NSApplication*)app openFile: (NSString*)pFile 227{ 228 (void)app; 229 std::vector<OUString> aFile; 230 aFile.push_back( GetOUString( pFile ) ); 231 if( ! AquaSalInstance::isOnCommandLine( aFile[0] ) ) 232 { 233 const ApplicationEvent* pAppEvent = new ApplicationEvent(ApplicationEvent::Type::Open, aFile); 234 AquaSalInstance::aAppEventList.push_back( pAppEvent ); 235 AquaSalInstance *pInst = GetSalData()->mpInstance; 236 if( pInst ) 237 pInst->TriggerUserEventProcessing(); 238 } 239 return YES; 240} 241 242-(void)application: (NSApplication*) app openFiles: (NSArray*)files 243{ 244 (void)app; 245 std::vector<OUString> aFileList; 246 247 NSEnumerator* it = [files objectEnumerator]; 248 NSString* pFile = nil; 249 250 while( (pFile = [it nextObject]) != nil ) 251 { 252 const OUString aFile( GetOUString( pFile ) ); 253 if( ! AquaSalInstance::isOnCommandLine( aFile ) ) 254 { 255 aFileList.push_back( aFile ); 256 } 257 } 258 259 if( !aFileList.empty() ) 260 { 261 // we have no back channel here, we have to assume success, in which case 262 // replyToOpenOrPrint does not need to be called according to documentation 263 // [app replyToOpenOrPrint: NSApplicationDelegateReplySuccess]; 264 const ApplicationEvent* pAppEvent = new ApplicationEvent(ApplicationEvent::Type::Open, aFileList); 265 AquaSalInstance::aAppEventList.push_back( pAppEvent ); 266 AquaSalInstance *pInst = GetSalData()->mpInstance; 267 if( pInst ) 268 pInst->TriggerUserEventProcessing(); 269 } 270} 271 272-(BOOL)application: (NSApplication*)app printFile: (NSString*)pFile 273{ 274 (void)app; 275 std::vector<OUString> aFile; 276 aFile.push_back( GetOUString( pFile ) ); 277 const ApplicationEvent* pAppEvent = new ApplicationEvent(ApplicationEvent::Type::Print, aFile); 278 AquaSalInstance::aAppEventList.push_back( pAppEvent ); 279 AquaSalInstance *pInst = GetSalData()->mpInstance; 280 if( pInst ) 281 pInst->TriggerUserEventProcessing(); 282 return YES; 283} 284-(NSApplicationPrintReply)application: (NSApplication *) app printFiles:(NSArray *)files withSettings: (NSDictionary *)printSettings showPrintPanels:(BOOL)bShowPrintPanels 285{ 286 (void)app; 287 (void)printSettings; 288 (void)bShowPrintPanels; 289 // currently ignores print settings a bShowPrintPanels 290 std::vector<OUString> aFileList; 291 292 NSEnumerator* it = [files objectEnumerator]; 293 NSString* pFile = nil; 294 295 while( (pFile = [it nextObject]) != nil ) 296 { 297 aFileList.push_back( GetOUString( pFile ) ); 298 } 299 const ApplicationEvent* pAppEvent = new ApplicationEvent(ApplicationEvent::Type::Print, aFileList); 300 AquaSalInstance::aAppEventList.push_back( pAppEvent ); 301 AquaSalInstance *pInst = GetSalData()->mpInstance; 302 if( pInst ) 303 pInst->TriggerUserEventProcessing(); 304 // we have no back channel here, we have to assume success 305 // correct handling would be NSPrintingReplyLater and then send [app replyToOpenOrPrint] 306 return NSPrintingSuccess; 307} 308 309-(void)applicationWillTerminate: (NSNotification *) aNotification 310{ 311 (void)aNotification; 312 sal_detail_deinitialize(); 313 _Exit(0); 314} 315 316-(NSApplicationTerminateReply)applicationShouldTerminate: (NSApplication *) app 317{ 318 (void)app; 319 NSApplicationTerminateReply aReply = NSTerminateNow; 320 { 321 SolarMutexGuard aGuard; 322 323 AquaSalInstance *pInst = GetSalData()->mpInstance; 324 SalFrame *pAnyFrame = pInst->anyFrame(); 325 if( pAnyFrame ) 326 { 327 // the following QueryExit will likely present a message box, activate application 328 [NSApp activateIgnoringOtherApps: YES]; 329 aReply = pAnyFrame->CallCallback( SalEvent::Shutdown, nullptr ) ? NSTerminateCancel : NSTerminateNow; 330 } 331 332 if( aReply == NSTerminateNow ) 333 { 334 ApplicationEvent aEv(ApplicationEvent::Type::PrivateDoShutdown); 335 GetpApp()->AppEvent( aEv ); 336 ImageTree::get().shutdown(); 337 // DeInitVCL should be called in ImplSVMain - unless someone exits first which 338 // can occur in Desktop::doShutdown for example 339 } 340 } 341 342 return aReply; 343} 344 345-(void)systemColorsChanged: (NSNotification*) pNotification 346{ 347 (void)pNotification; 348 SolarMutexGuard aGuard; 349 350 AquaSalInstance *pInst = GetSalData()->mpInstance; 351 SalFrame *pAnyFrame = pInst->anyFrame(); 352 if( pAnyFrame ) 353 pAnyFrame->CallCallback( SalEvent::SettingsChanged, nullptr ); 354} 355 356-(void)screenParametersChanged: (NSNotification*) pNotification 357{ 358 (void)pNotification; 359 SolarMutexGuard aGuard; 360 361 for( auto pSalFrame : GetSalData()->mpInstance->getFrames() ) 362 { 363 AquaSalFrame *pFrame = static_cast<AquaSalFrame*>( pSalFrame ); 364 pFrame->screenParametersChanged(); 365 } 366} 367 368-(void)scrollbarVariantChanged: (NSNotification*) pNotification 369{ 370 (void)pNotification; 371 GetSalData()->mpInstance->delayedSettingsChanged( true ); 372} 373 374-(void)scrollbarSettingsChanged: (NSNotification*) pNotification 375{ 376 (void)pNotification; 377 GetSalData()->mpInstance->delayedSettingsChanged( false ); 378} 379 380-(void)addFallbackMenuItem: (NSMenuItem*)pNewItem 381{ 382 AquaSalMenu::addFallbackMenuItem( pNewItem ); 383} 384 385-(void)removeFallbackMenuItem: (NSMenuItem*)pItem 386{ 387 AquaSalMenu::removeFallbackMenuItem( pItem ); 388} 389 390-(void)addDockMenuItem: (NSMenuItem*)pNewItem 391{ 392 NSMenu* pDock = AquaSalInstance::GetDynamicDockMenu(); 393 [pDock insertItem: pNewItem atIndex: [pDock numberOfItems]]; 394} 395 396// for Apple Remote implementation 397 398#if !HAVE_FEATURE_MACOSX_SANDBOX 399- (void)applicationWillBecomeActive:(NSNotification *)pNotification 400{ 401 (void)pNotification; 402 SalData* pSalData = GetSalData(); 403 AppleRemoteMainController* pAppleRemoteCtrl = pSalData->mpAppleRemoteMainController; 404 if( pAppleRemoteCtrl && pAppleRemoteCtrl->remoteControl) 405 { 406 // [remoteControl startListening: self]; 407 // does crash because the right thing to do is 408 // [pAppleRemoteCtrl->remoteControl startListening: self]; 409 // but the instance variable 'remoteControl' is declared protected 410 // workaround : declare remoteControl instance variable as public in RemoteMainController.m 411 412 [pAppleRemoteCtrl->remoteControl startListening: self]; 413#ifdef DEBUG 414 NSLog(@"Apple Remote will become active - Using remote controls"); 415#endif 416 } 417 for( std::list< AquaSalFrame* >::const_iterator it = pSalData->maPresentationFrames.begin(); 418 it != pSalData->maPresentationFrames.end(); ++it ) 419 { 420 NSWindow* pNSWindow = (*it)->getNSWindow(); 421 [pNSWindow setLevel: NSPopUpMenuWindowLevel]; 422 if( [pNSWindow isVisible] ) 423 [pNSWindow orderFront: NSApp]; 424 } 425} 426 427- (void)applicationWillResignActive:(NSNotification *)pNotification 428{ 429 (void)pNotification; 430 SalData* pSalData = GetSalData(); 431 AppleRemoteMainController* pAppleRemoteCtrl = pSalData->mpAppleRemoteMainController; 432 if( pAppleRemoteCtrl && pAppleRemoteCtrl->remoteControl) 433 { 434 // [remoteControl stopListening: self]; 435 // does crash because the right thing to do is 436 // [pAppleRemoteCtrl->remoteControl stopListening: self]; 437 // but the instance variable 'remoteControl' is declared protected 438 // workaround : declare remoteControl instance variable as public in RemoteMainController.m 439 440 [pAppleRemoteCtrl->remoteControl stopListening: self]; 441#ifdef DEBUG 442 NSLog(@"Apple Remote will resign active - Releasing remote controls"); 443#endif 444 } 445 for( std::list< AquaSalFrame* >::const_iterator it = pSalData->maPresentationFrames.begin(); 446 it != pSalData->maPresentationFrames.end(); ++it ) 447 { 448 [(*it)->getNSWindow() setLevel: NSNormalWindowLevel]; 449 } 450} 451#endif 452 453- (BOOL)applicationShouldHandleReopen: (NSApplication*)pApp hasVisibleWindows: (BOOL) bWinVisible 454{ 455 (void)pApp; 456 (void)bWinVisible; 457 NSObject* pHdl = GetSalData()->mpDockIconClickHandler; 458 if( pHdl && [pHdl respondsToSelector: @selector(dockIconClicked:)] ) 459 { 460 [pHdl performSelector:@selector(dockIconClicked:) withObject: self]; 461 } 462 return YES; 463} 464 465-(void)setDockIconClickHandler: (NSObject*)pHandler 466{ 467 GetSalData()->mpDockIconClickHandler = pHandler; 468} 469 470 471@end 472 473/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ 474