/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #import "Carbon/Carbon.h" #import "apple_remote/RemoteControl.h" #include @implementation CocoaThreadEnabler -(void)enableCocoaThreads:(id)param { // do nothing, this is just to start an NSThread and therefore put // Cocoa into multithread mode (void)param; } @end // If you wonder how this VCL_NSApplication stuff works, one thing you // might have missed is that the NSPrincipalClass property in // desktop/macosx/Info.plist has the value VCL_NSApplication. @implementation VCL_NSApplication -(void)applicationDidFinishLaunching:(NSNotification*)pNotification { (void)pNotification; NSEvent* pEvent = [NSEvent otherEventWithType: NSEventTypeApplicationDefined location: NSZeroPoint modifierFlags: 0 timestamp: [[NSProcessInfo processInfo] systemUptime] windowNumber: 0 context: nil subtype: AquaSalInstance::AppExecuteSVMain data1: 0 data2: 0 ]; assert( pEvent ); [NSApp postEvent: pEvent atStart: NO]; if( [NSWindow respondsToSelector:@selector(allowsAutomaticWindowTabbing)] ) { [NSWindow setAllowsAutomaticWindowTabbing:NO]; } // listen to dark mode change [NSApp addObserver:self forKeyPath:@"effectiveAppearance" options: 0 context: nil]; } -(void)sendEvent:(NSEvent*)pEvent { NSEventType eType = [pEvent type]; if( eType == NSEventTypeApplicationDefined ) { AquaSalInstance::handleAppDefinedEvent( pEvent ); } else if( eType == NSEventTypeKeyDown && ([pEvent modifierFlags] & NSEventModifierFlagCommand) != 0 ) { NSWindow* pKeyWin = [NSApp keyWindow]; if( pKeyWin && [pKeyWin isKindOfClass: [SalFrameWindow class]] ) { // Commit uncommitted text before dispatching key shortcuts. In // certain cases such as pressing Command-Option-C in a Writer // document while there is uncommitted text will call // AquaSalFrame::EndExtTextInput() which will dispatch a // SalEvent::EndExtTextInput event. Writer's handler for that event // will delete the uncommitted text and then insert the committed // text but LibreOffice will crash when deleting the uncommitted // text because deletion of the text also removes and deletes the // newly inserted comment. [static_cast(pKeyWin) endExtTextInput]; AquaSalFrame* pFrame = [static_cast(pKeyWin) getSalFrame]; // Related tdf#162010: match against -[NSEvent characters] // When using some non-Western European keyboard layouts, the // event's "characters ignoring modifiers" will be set to the // original Unicode character instead of the resolved key // equivalent character so match against the -[NSEvent characters] // instead. NSEventModifierFlags nModMask = ([pEvent modifierFlags] & (NSEventModifierFlagShift|NSEventModifierFlagControl|NSEventModifierFlagOption|NSEventModifierFlagCommand)); // Note: when pressing Command-Option keys, some non-Western // keyboards will set the "characters ignoring modifiers" // property to the key shortcut character instead of setting // the "characters property. So check for both cases. NSString *pCharacters = [pEvent characters]; NSString *pCharactersIgnoringModifiers = [pEvent charactersIgnoringModifiers]; /* * #i98949# - Cmd-M miniaturize window, Cmd-Option-M miniaturize all windows */ if( [pCharacters isEqualToString: @"m"] || [pCharactersIgnoringModifiers isEqualToString: @"m"] ) { if ( nModMask == NSEventModifierFlagCommand && ([pFrame->getNSWindow() styleMask] & NSWindowStyleMaskMiniaturizable) ) { [pFrame->getNSWindow() performMiniaturize: nil]; return; } else if ( nModMask == ( NSEventModifierFlagCommand | NSEventModifierFlagOption ) ) { [NSApp miniaturizeAll: nil]; return; } } // tdf#162190 handle Command-w // On macOS, Command-w should attempt to close the key window. // TODO: Command-Option-w should attempt to close all windows. else if( [pCharacters isEqualToString: @"w"] || [pCharactersIgnoringModifiers isEqualToString: @"w"] ) { if ( nModMask == NSEventModifierFlagCommand && ([pFrame->getNSWindow() styleMask] & NSWindowStyleMaskClosable ) ) { [pFrame->getNSWindow() performClose: nil]; return; } } // get information whether the event was handled; keyDown returns nothing GetSalData()->maKeyEventAnswer[ pEvent ] = false; bool bHandled = false; // dispatch to view directly to avoid the key event being consumed by the menubar // popup windows do not get the focus, so they don't get these either // simplest would be dispatch this to the key window always if it is without parent // however e.g. in document we want the menu shortcut if e.g. the stylist has focus if( pFrame->mpParent && !(pFrame->mnStyle & SalFrameStyleFlags::FLOAT) ) { [[pKeyWin contentView] keyDown: pEvent]; bHandled = GetSalData()->maKeyEventAnswer[ pEvent ]; } // see whether the main menu consumes this event // if not, we want to dispatch it ourselves. Unless we do this "trick" // the main menu just beeps for an unknown or disabled key equivalent // and swallows the event wholesale NSMenu* pMainMenu = [NSApp mainMenu]; if( ! bHandled && (pMainMenu == nullptr || ! [NSMenu menuBarVisible] || ! [pMainMenu performKeyEquivalent: pEvent]) ) { [[pKeyWin contentView] keyDown: pEvent]; bHandled = GetSalData()->maKeyEventAnswer[ pEvent ]; } else { bHandled = true; // event handled already or main menu just handled it } GetSalData()->maKeyEventAnswer.erase( pEvent ); if( bHandled ) return; } else if( pKeyWin ) { // #i94601# a window not of vcl's making has the focus. // Since our menus do not invoke the usual commands // try to play nice with native windows like the file dialog // and emulate them // precondition: this ONLY works because CMD-V (paste), CMD-C (copy) and CMD-X (cut) are // NOT localized, that is the same in all locales. Should this be // different in any locale, this hack will fail. if( [SalNSMenu dispatchSpecialKeyEquivalents:pEvent] ) return; } } [super sendEvent: pEvent]; } -(void)sendSuperEvent:(NSEvent*)pEvent { [super sendEvent: pEvent]; } -(NSMenu*)applicationDockMenu:(NSApplication *)sender { (void)sender; return AquaSalInstance::GetDynamicDockMenu(); } -(BOOL)application: (NSApplication*)app openFile: (NSString*)pFile { (void)app; std::vector aFile { GetOUString( pFile ) }; if( ! AquaSalInstance::isOnCommandLine( aFile[0] ) ) { const ApplicationEvent* pAppEvent = new ApplicationEvent(ApplicationEvent::Type::Open, std::move(aFile)); AquaSalInstance::aAppEventList.push_back( pAppEvent ); AquaSalInstance *pInst = GetSalData()->mpInstance; if( pInst ) pInst->TriggerUserEventProcessing(); } return YES; } -(void)application: (NSApplication*) app openFiles: (NSArray*)files { (void)app; std::vector aFileList; NSEnumerator* it = [files objectEnumerator]; NSString* pFile = nil; while( (pFile = [it nextObject]) != nil ) { const OUString aFile( GetOUString( pFile ) ); if( ! AquaSalInstance::isOnCommandLine( aFile ) ) { aFileList.push_back( aFile ); } } if( !aFileList.empty() ) { // we have no back channel here, we have to assume success, in which case // replyToOpenOrPrint does not need to be called according to documentation // [app replyToOpenOrPrint: NSApplicationDelegateReplySuccess]; const ApplicationEvent* pAppEvent = new ApplicationEvent(ApplicationEvent::Type::Open, std::move(aFileList)); AquaSalInstance::aAppEventList.push_back( pAppEvent ); AquaSalInstance *pInst = GetSalData()->mpInstance; if( pInst ) pInst->TriggerUserEventProcessing(); } } -(BOOL)application: (NSApplication*)app printFile: (NSString*)pFile { (void)app; std::vector aFile { GetOUString(pFile) }; const ApplicationEvent* pAppEvent = new ApplicationEvent(ApplicationEvent::Type::Print, std::move(aFile)); AquaSalInstance::aAppEventList.push_back( pAppEvent ); AquaSalInstance *pInst = GetSalData()->mpInstance; if( pInst ) pInst->TriggerUserEventProcessing(); return YES; } -(NSApplicationPrintReply)application: (NSApplication *) app printFiles:(NSArray *)files withSettings: (NSDictionary *)printSettings showPrintPanels:(BOOL)bShowPrintPanels { (void)app; (void)printSettings; (void)bShowPrintPanels; // currently ignores print settings a bShowPrintPanels std::vector aFileList; NSEnumerator* it = [files objectEnumerator]; NSString* pFile = nil; while( (pFile = [it nextObject]) != nil ) { aFileList.push_back( GetOUString( pFile ) ); } const ApplicationEvent* pAppEvent = new ApplicationEvent(ApplicationEvent::Type::Print, std::move(aFileList)); AquaSalInstance::aAppEventList.push_back( pAppEvent ); AquaSalInstance *pInst = GetSalData()->mpInstance; if( pInst ) pInst->TriggerUserEventProcessing(); // we have no back channel here, we have to assume success // correct handling would be NSPrintingReplyLater and then send [app replyToOpenOrPrint] return NSPrintingSuccess; } -(void)applicationWillTerminate: (NSNotification *) aNotification { (void)aNotification; sal_detail_deinitialize(); _Exit(0); } -(NSApplicationTerminateReply)applicationShouldTerminate: (NSApplication *) app { (void)app; // Related: tdf#126638 disable all menu items when displaying modal windows // Although -[SalNSMenuItem validateMenuItem:] disables almost all menu // items when a modal window is displayed, the standard Quit menu item // does not get disabled so disable it here. if ([NSApp modalWindow]) return NSTerminateCancel; NSApplicationTerminateReply aReply = NSTerminateNow; { SolarMutexGuard aGuard; AquaSalInstance *pInst = GetSalData()->mpInstance; SalFrame *pAnyFrame = pInst->anyFrame(); if( pAnyFrame ) { // the following QueryExit will likely present a message box, activate application [NSApp activateIgnoringOtherApps: YES]; aReply = pAnyFrame->CallCallback( SalEvent::Shutdown, nullptr ) ? NSTerminateCancel : NSTerminateNow; } if( aReply == NSTerminateNow ) { ApplicationEvent aEv(ApplicationEvent::Type::PrivateDoShutdown); GetpApp()->AppEvent( aEv ); ImageTree::get().shutdown(); // DeInitVCL should be called in ImplSVMain - unless someone exits first which // can occur in Desktop::doShutdown for example } } return aReply; } -(void)observeValueForKeyPath: (NSString*) keyPath ofObject:(id)object change: (NSDictionary*)change context: (void*)context { [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; if ([keyPath isEqualToString:@"effectiveAppearance"]) [self systemColorsChanged: nil]; } -(void)systemColorsChanged: (NSNotification*) pNotification { (void)pNotification; SolarMutexGuard aGuard; AquaSalInstance *pInst = GetSalData()->mpInstance; SalFrame *pAnyFrame = pInst->anyFrame(); if( pAnyFrame ) pAnyFrame->CallCallback( SalEvent::SettingsChanged, nullptr ); } -(void)screenParametersChanged: (NSNotification*) pNotification { (void)pNotification; SolarMutexGuard aGuard; for( auto pSalFrame : GetSalData()->mpInstance->getFrames() ) { AquaSalFrame *pFrame = static_cast( pSalFrame ); pFrame->screenParametersChanged(); } } -(void)scrollbarVariantChanged: (NSNotification*) pNotification { (void)pNotification; GetSalData()->mpInstance->delayedSettingsChanged( true ); } -(void)scrollbarSettingsChanged: (NSNotification*) pNotification { (void)pNotification; GetSalData()->mpInstance->delayedSettingsChanged( false ); } -(void)addFallbackMenuItem: (NSMenuItem*)pNewItem { AquaSalMenu::addFallbackMenuItem( pNewItem ); } -(void)removeFallbackMenuItem: (NSMenuItem*)pItem { AquaSalMenu::removeFallbackMenuItem( pItem ); } -(void)addDockMenuItem: (NSMenuItem*)pNewItem { NSMenu* pDock = AquaSalInstance::GetDynamicDockMenu(); [pDock insertItem: pNewItem atIndex: [pDock numberOfItems]]; } // for Apple Remote implementation #if !HAVE_FEATURE_MACOSX_SANDBOX - (void)applicationWillBecomeActive:(NSNotification *)pNotification { (void)pNotification; SalData* pSalData = GetSalData(); AppleRemoteMainController* pAppleRemoteCtrl = pSalData->mpAppleRemoteMainController; if( pAppleRemoteCtrl && pAppleRemoteCtrl->remoteControl) { // [remoteControl startListening: self]; // does crash because the right thing to do is // [pAppleRemoteCtrl->remoteControl startListening: self]; // but the instance variable 'remoteControl' is declared protected // workaround : declare remoteControl instance variable as public in RemoteMainController.m [pAppleRemoteCtrl->remoteControl startListening: self]; #if OSL_DEBUG_LEVEL >= 2 NSLog(@"Apple Remote will become active - Using remote controls"); #endif } for( std::list< AquaSalFrame* >::const_iterator it = pSalData->maPresentationFrames.begin(); it != pSalData->maPresentationFrames.end(); ++it ) { NSWindow* pNSWindow = (*it)->getNSWindow(); [pNSWindow setLevel: NSPopUpMenuWindowLevel]; if( [pNSWindow isVisible] ) [pNSWindow orderFront: NSApp]; } } - (void)applicationWillResignActive:(NSNotification *)pNotification { (void)pNotification; SalData* pSalData = GetSalData(); AppleRemoteMainController* pAppleRemoteCtrl = pSalData->mpAppleRemoteMainController; if( pAppleRemoteCtrl && pAppleRemoteCtrl->remoteControl) { // [remoteControl stopListening: self]; // does crash because the right thing to do is // [pAppleRemoteCtrl->remoteControl stopListening: self]; // but the instance variable 'remoteControl' is declared protected // workaround : declare remoteControl instance variable as public in RemoteMainController.m [pAppleRemoteCtrl->remoteControl stopListening: self]; #if OSL_DEBUG_LEVEL >= 2 NSLog(@"Apple Remote will resign active - Releasing remote controls"); #endif } for( std::list< AquaSalFrame* >::const_iterator it = pSalData->maPresentationFrames.begin(); it != pSalData->maPresentationFrames.end(); ++it ) { [(*it)->getNSWindow() setLevel: NSNormalWindowLevel]; } } #endif - (BOOL)applicationShouldHandleReopen: (NSApplication*)pApp hasVisibleWindows: (BOOL) bWinVisible { (void)pApp; (void)bWinVisible; NSObject* pHdl = GetSalData()->mpDockIconClickHandler; if( pHdl && [pHdl respondsToSelector: @selector(dockIconClicked:)] ) { [pHdl performSelector:@selector(dockIconClicked:) withObject: self]; } return YES; } - (BOOL)applicationSupportsSecureRestorableState: (NSApplication *)pApp { return YES; } -(void)setDockIconClickHandler: (NSObject*)pHandler { GetSalData()->mpDockIconClickHandler = pHandler; } @end /* vim:set shiftwidth=4 softtabstop=4 expandtab: */