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 <sal/log.hxx>
22 #include <osl/diagnose.h>
23 
24 #include <condition_variable>
25 #include <mutex>
26 #include <utility>
27 
28 #include <config_features.h>
29 
30 #include <stdio.h>
31 
32 #include <comphelper/solarmutex.hxx>
33 
34 #include <comphelper/lok.hxx>
35 
36 #include <osl/process.h>
37 
38 #include <rtl/ustrbuf.hxx>
39 #include <vclpluginapi.h>
40 #include <vcl/QueueInfo.hxx>
41 #include <vcl/svapp.hxx>
42 #include <vcl/window.hxx>
43 #include <vcl/idle.hxx>
44 #include <vcl/svmain.hxx>
45 #include <vcl/opengl/OpenGLContext.hxx>
46 #include <vcl/commandevent.hxx>
47 #include <vcl/event.hxx>
48 
49 #include <osx/saldata.hxx>
50 #include <osx/salinst.h>
51 #include <osx/salframe.h>
52 #include <osx/salobj.h>
53 #include <osx/salsys.h>
54 #include <quartz/salvd.h>
55 #include <quartz/salbmp.h>
56 #include <quartz/utils.h>
57 #include <osx/salprn.h>
58 #include <osx/saltimer.h>
59 #include <osx/vclnsapp.h>
60 #include <osx/runinmain.hxx>
61 
62 #include <print.h>
63 
64 #include <comphelper/processfactory.hxx>
65 
66 #include <com/sun/star/uri/ExternalUriReferenceTranslator.hpp>
67 #include <com/sun/star/uno/XComponentContext.hpp>
68 
69 #include <premac.h>
70 #include <Foundation/Foundation.h>
71 #include <ApplicationServices/ApplicationServices.h>
72 #import "apple_remote/RemoteMainController.h"
73 #include <apple_remote/RemoteControl.h>
74 #include <postmac.h>
75 
76 extern "C" {
77 #include <crt_externs.h>
78 }
79 
80 using namespace std;
81 using namespace ::com::sun::star;
82 
83 static int* gpnInit = nullptr;
84 static NSMenu* pDockMenu = nil;
85 static bool bLeftMain = false;
86 
87 namespace {
88 
89 class AquaDelayedSettingsChanged : public Idle
90 {
91     bool            mbInvalidate;
92 
93 public:
AquaDelayedSettingsChanged(bool bInvalidate)94     AquaDelayedSettingsChanged( bool bInvalidate ) :
95         mbInvalidate( bInvalidate )
96     {
97     }
98 
Invoke()99     virtual void Invoke() override
100     {
101         AquaSalInstance *pInst = GetSalData()->mpInstance;
102         SalFrame *pAnyFrame = pInst->anyFrame();
103         if( pAnyFrame )
104             pAnyFrame->CallCallback( SalEvent::SettingsChanged, nullptr );
105 
106         if( mbInvalidate )
107         {
108             for( auto pSalFrame : pInst->getFrames() )
109             {
110                 AquaSalFrame* pFrame = static_cast<AquaSalFrame*>( pSalFrame );
111                 if( pFrame->mbShown )
112                     pFrame->SendPaintEvent();
113             }
114         }
115         delete this;
116     }
117 };
118 
119 }
120 
delayedSettingsChanged(bool bInvalidate)121 void AquaSalInstance::delayedSettingsChanged( bool bInvalidate )
122 {
123     osl::Guard< comphelper::SolarMutex > aGuard( *GetYieldMutex() );
124     AquaDelayedSettingsChanged* pIdle = new AquaDelayedSettingsChanged( bInvalidate );
125     pIdle->SetDebugName( "AquaSalInstance AquaDelayedSettingsChanged" );
126     pIdle->Start();
127 }
128 
129 // the std::list<const ApplicationEvent*> must be available before any SalData/SalInst/etc. objects are ready
130 std::list<const ApplicationEvent*> AquaSalInstance::aAppEventList;
131 
GetDynamicDockMenu()132 NSMenu* AquaSalInstance::GetDynamicDockMenu()
133 {
134     if( ! pDockMenu && ! bLeftMain )
135         pDockMenu = [[NSMenu alloc] initWithTitle: @""];
136     return pDockMenu;
137 }
138 
isOnCommandLine(const OUString & rArg)139 bool AquaSalInstance::isOnCommandLine( const OUString& rArg )
140 {
141     sal_uInt32 nArgs = osl_getCommandArgCount();
142     for( sal_uInt32 i = 0; i < nArgs; i++ )
143     {
144         OUString aArg;
145         osl_getCommandArg( i, &aArg.pData );
146         if( aArg.equals( rArg ) )
147             return true;
148     }
149     return false;
150 }
151 
AfterAppInit()152 void AquaSalInstance::AfterAppInit()
153 {
154     [[NSNotificationCenter defaultCenter] addObserver: NSApp
155                                           selector: @selector(systemColorsChanged:)
156                                           name: NSSystemColorsDidChangeNotification
157                                           object: nil ];
158     [[NSNotificationCenter defaultCenter] addObserver: NSApp
159                                           selector: @selector(screenParametersChanged:)
160                                           name: NSApplicationDidChangeScreenParametersNotification
161                                           object: nil ];
162     // add observers for some settings changes that affect vcl's settings
163     // scrollbar variant
164     [[NSDistributedNotificationCenter defaultCenter] addObserver: NSApp
165                                           selector: @selector(scrollbarVariantChanged:)
166                                           name: @"AppleAquaScrollBarVariantChanged"
167                                           object: nil ];
168     // scrollbar page behavior ("jump to here" or not)
169     [[NSDistributedNotificationCenter defaultCenter] addObserver: NSApp
170                                           selector: @selector(scrollbarSettingsChanged:)
171                                           name: @"AppleNoRedisplayAppearancePreferenceChanged"
172                                           object: nil ];
173 #if !HAVE_FEATURE_MACOSX_SANDBOX
174     // Initialize Apple Remote
175     GetSalData()->mpAppleRemoteMainController = [[AppleRemoteMainController alloc] init];
176 
177     [[NSDistributedNotificationCenter defaultCenter] addObserver: NSApp
178                                            selector: @selector(applicationWillBecomeActive:)
179                                            name: @"AppleRemoteWillBecomeActive"
180                                            object: nil ];
181 
182     [[NSDistributedNotificationCenter defaultCenter] addObserver: NSApp
183                                            selector: @selector(applicationWillResignActive:)
184                                            name: @"AppleRemoteWillResignActive"
185                                            object: nil ];
186 #endif
187 }
188 
SalYieldMutex()189 SalYieldMutex::SalYieldMutex()
190     : m_aCodeBlock( nullptr )
191 {
192 }
193 
~SalYieldMutex()194 SalYieldMutex::~SalYieldMutex()
195 {
196 }
197 
doAcquire(sal_uInt32 nLockCount)198 void SalYieldMutex::doAcquire( sal_uInt32 nLockCount )
199 {
200     AquaSalInstance *pInst = GetSalData()->mpInstance;
201     if ( pInst && pInst->IsMainThread() )
202     {
203         if ( pInst->mbNoYieldLock )
204             return;
205         do {
206             RuninmainBlock block = nullptr;
207             {
208                 std::unique_lock<std::mutex> g(m_runInMainMutex);
209                 if (m_aMutex.tryToAcquire()) {
210                     assert(m_aCodeBlock == nullptr);
211                     m_wakeUpMain = false;
212                     break;
213                 }
214                 // wait for doRelease() or RUNINMAIN_* to set the condition
215                 m_aInMainCondition.wait(g, [this]() { return m_wakeUpMain; });
216                 m_wakeUpMain = false;
217                 std::swap(block, m_aCodeBlock);
218             }
219             if ( block )
220             {
221                 assert( !pInst->mbNoYieldLock );
222                 pInst->mbNoYieldLock = true;
223                 block();
224                 pInst->mbNoYieldLock = false;
225                 Block_release( block );
226                 std::scoped_lock<std::mutex> g(m_runInMainMutex);
227                 assert(!m_resultReady);
228                 m_resultReady = true;
229                 m_aResultCondition.notify_all();
230             }
231         }
232         while ( true );
233     }
234     else
235         m_aMutex.acquire();
236     ++m_nCount;
237     --nLockCount;
238 
239     comphelper::SolarMutex::doAcquire( nLockCount );
240 }
241 
doRelease(const bool bUnlockAll)242 sal_uInt32 SalYieldMutex::doRelease( const bool bUnlockAll )
243 {
244     AquaSalInstance *pInst = GetSalData()->mpInstance;
245     if ( pInst->mbNoYieldLock && pInst->IsMainThread() )
246         return 1;
247     sal_uInt32 nCount;
248     {
249         std::scoped_lock<std::mutex> g(m_runInMainMutex);
250         // read m_nCount before doRelease
251         bool const isReleased(bUnlockAll || m_nCount == 1);
252         nCount = comphelper::SolarMutex::doRelease( bUnlockAll );
253         if (isReleased && !pInst->IsMainThread()) {
254             m_wakeUpMain = true;
255             m_aInMainCondition.notify_all();
256         }
257     }
258     return nCount;
259 }
260 
IsCurrentThread() const261 bool SalYieldMutex::IsCurrentThread() const
262 {
263     if ( !GetSalData()->mpInstance->mbNoYieldLock )
264         return comphelper::SolarMutex::IsCurrentThread();
265     else
266         return GetSalData()->mpInstance->IsMainThread();
267 }
268 
269 // some convenience functions regarding the yield mutex, aka solar mutex
270 
ImplSalYieldMutexTryToAcquire()271 bool ImplSalYieldMutexTryToAcquire()
272 {
273     AquaSalInstance* pInst = GetSalData()->mpInstance;
274     if ( pInst )
275         return pInst->GetYieldMutex()->tryToAcquire();
276     else
277         return false;
278 }
279 
ImplSalYieldMutexRelease()280 void ImplSalYieldMutexRelease()
281 {
282     AquaSalInstance* pInst = GetSalData()->mpInstance;
283     if ( pInst )
284         pInst->GetYieldMutex()->release();
285 }
286 
287 extern "C" {
create_SalInstance()288 VCLPLUG_OSX_PUBLIC SalInstance* create_SalInstance()
289 {
290     SalData* pSalData = new SalData;
291 
292     NSAutoreleasePool * pool = [ [ NSAutoreleasePool alloc ] init ];
293     unlink([[NSString stringWithFormat:@"%@/Library/Saved Application State/%s.savedState/restorecount.plist", NSHomeDirectory(), MACOSX_BUNDLE_IDENTIFIER] UTF8String]);
294     unlink([[NSString stringWithFormat:@"%@/Library/Saved Application State/%s.savedState/restorecount.txt", NSHomeDirectory(), MACOSX_BUNDLE_IDENTIFIER] UTF8String]);
295     [ pool drain ];
296 
297     // create our cocoa NSApplication
298     [VCL_NSApplication sharedApplication];
299 
300     SalData::ensureThreadAutoreleasePool();
301 
302     // put cocoa into multithreaded mode
303     [NSThread detachNewThreadSelector:@selector(enableCocoaThreads:) toTarget:[[CocoaThreadEnabler alloc] init] withObject:nil];
304 
305     // Dark mode is disabled as long as it is not implemented completely. For development purposes, it may be controlled by
306     // environment variables: VCL_MACOS_FORCE_DARK_MODE enable dark mode independent of system settings,
307     // VCL_MACOS_USE_SYSTEM_APPEARANCE to use system settings (light mode or the dark mode as configured within system preferences).
308 
309     // TODO: After implementation of dark mode, this code has to be removed.
310 
311     if (@available(macOS 10.14, iOS 13, *))
312     {
313         if (getenv("VCL_MACOS_FORCE_DARK_MODE"))
314         {
315             [NSApp setAppearance: [NSAppearance appearanceNamed: NSAppearanceNameDarkAqua]];
316         }
317         else
318             if (!getenv("VCL_MACOS_USE_SYSTEM_APPEARANCE"))
319                 [NSApp setAppearance: [NSAppearance appearanceNamed: NSAppearanceNameAqua]];
320     }
321 
322     // activate our delegate methods
323     [NSApp setDelegate: NSApp];
324 
325     SAL_WARN_IF( pSalData->mpInstance != nullptr, "vcl", "more than one instance created" );
326     AquaSalInstance* pInst = new AquaSalInstance;
327 
328     // init instance (only one instance in this version !!!)
329     pSalData->mpInstance = pInst;
330     // this one is for outside AquaSalInstance::Yield
331     SalData::ensureThreadAutoreleasePool();
332     // no focus rects on NWF
333     ImplGetSVData()->maNWFData.mbNoFocusRects = true;
334     ImplGetSVData()->maNWFData.mbNoActiveTabTextRaise = true;
335     ImplGetSVData()->maNWFData.mbCenteredTabs = true;
336     ImplGetSVData()->maNWFData.mnStatusBarLowerRightOffset = 10;
337 
338     return pInst;
339 }
340 }
341 
AquaSalInstance()342 AquaSalInstance::AquaSalInstance()
343     : SalInstance(std::make_unique<SalYieldMutex>())
344     , mnActivePrintJobs( 0 )
345     , mbIsLiveResize( false )
346     , mbNoYieldLock( false )
347     , mbTimerProcessed( false )
348 {
349     maMainThread = osl::Thread::getCurrentIdentifier();
350 
351     ImplSVData* pSVData = ImplGetSVData();
352     pSVData->maAppData.mxToolkitName = OUString("osx");
353     m_bSupportsOpenGL = true;
354 }
355 
~AquaSalInstance()356 AquaSalInstance::~AquaSalInstance()
357 {
358     [NSApp stop: NSApp];
359     bLeftMain = true;
360     if( pDockMenu )
361     {
362         [pDockMenu release];
363         pDockMenu = nil;
364     }
365 }
366 
TriggerUserEventProcessing()367 void AquaSalInstance::TriggerUserEventProcessing()
368 {
369     dispatch_async(dispatch_get_main_queue(),^{
370         ImplNSAppPostEvent( AquaSalInstance::YieldWakeupEvent, NO );
371     });
372 }
373 
ProcessEvent(SalUserEvent aEvent)374 void AquaSalInstance::ProcessEvent( SalUserEvent aEvent )
375 {
376     aEvent.m_pFrame->CallCallback( aEvent.m_nEvent, aEvent.m_pData );
377     maWaitingYieldCond.set();
378 }
379 
IsMainThread() const380 bool AquaSalInstance::IsMainThread() const
381 {
382     return osl::Thread::getCurrentIdentifier() == maMainThread;
383 }
384 
handleAppDefinedEvent(NSEvent * pEvent)385 void AquaSalInstance::handleAppDefinedEvent( NSEvent* pEvent )
386 {
387     AquaSalTimer *pTimer = static_cast<AquaSalTimer*>( ImplGetSVData()->maSchedCtx.mpSalTimer );
388     int nSubtype = [pEvent subtype];
389     switch( nSubtype )
390     {
391     case AppStartTimerEvent:
392         if ( pTimer )
393             pTimer->handleStartTimerEvent( pEvent );
394         break;
395     case AppExecuteSVMain:
396     {
397         int nRet = ImplSVMain();
398         if (gpnInit)
399             *gpnInit = nRet;
400         [NSApp stop: NSApp];
401         break;
402     }
403     case DispatchTimerEvent:
404     {
405         AquaSalInstance *pInst = GetSalData()->mpInstance;
406         if ( pTimer && pInst )
407             pInst->mbTimerProcessed = pTimer->handleDispatchTimerEvent( pEvent );
408         break;
409     }
410 #if !HAVE_FEATURE_MACOSX_SANDBOX
411     case AppleRemoteControlEvent: // Defined in <apple_remote/RemoteMainController.h>
412     {
413         MediaCommand nCommand;
414         AquaSalInstance *pInst = GetSalData()->mpInstance;
415         bool bIsFullScreenMode = false;
416 
417         for( auto pSalFrame : pInst->getFrames() )
418         {
419             const AquaSalFrame* pFrame = static_cast<const AquaSalFrame*>( pSalFrame );
420             if ( pFrame->mbFullScreen )
421             {
422                 bIsFullScreenMode = true;
423                 break;
424             }
425         }
426 
427         switch ([pEvent data1])
428         {
429             case kRemoteButtonPlay:
430                 nCommand = bIsFullScreenMode ? MediaCommand::PlayPause : MediaCommand::Play;
431                 break;
432 
433             // kept for experimentation purpose (scheduled for future implementation)
434             // case kRemoteButtonMenu:         nCommand = MediaCommand::Menu; break;
435 
436             case kRemoteButtonPlus:         nCommand = MediaCommand::VolumeUp; break;
437 
438             case kRemoteButtonMinus:        nCommand = MediaCommand::VolumeDown; break;
439 
440             case kRemoteButtonRight:        nCommand = MediaCommand::NextTrack; break;
441 
442             case kRemoteButtonRight_Hold:   nCommand = MediaCommand::NextTrackHold; break;
443 
444             case kRemoteButtonLeft:         nCommand = MediaCommand::PreviousTrack; break;
445 
446             case kRemoteButtonLeft_Hold:    nCommand = MediaCommand::Rewind; break;
447 
448             case kRemoteButtonPlay_Hold:    nCommand = MediaCommand::PlayHold; break;
449 
450             case kRemoteButtonMenu_Hold:    nCommand = MediaCommand::Stop; break;
451 
452             // FIXME : not detected
453             case kRemoteButtonPlus_Hold:
454             case kRemoteButtonMinus_Hold:
455                 break;
456 
457             default:
458                 break;
459         }
460         AquaSalFrame* pFrame = static_cast<AquaSalFrame*>( pInst->anyFrame() );
461         vcl::Window* pWindow = pFrame ? pFrame->GetWindow() : nullptr;
462         if( pWindow )
463         {
464             const Point aPoint;
465             CommandMediaData aMediaData(nCommand);
466             CommandEvent aCEvt( aPoint, CommandEventId::Media, false, &aMediaData );
467             NotifyEvent aNCmdEvt( MouseNotifyEvent::COMMAND, pWindow, &aCEvt );
468 
469             if ( !ImplCallPreNotify( aNCmdEvt ) )
470                 pWindow->Command( aCEvt );
471         }
472 
473     }
474     break;
475 #endif
476 
477     case YieldWakeupEvent:
478         // do nothing, fall out of Yield
479         break;
480 
481     default:
482         OSL_FAIL( "unhandled NSApplicationDefined event" );
483         break;
484     }
485 }
486 
RunInMainYield(bool bHandleAllCurrentEvents)487 bool AquaSalInstance::RunInMainYield( bool bHandleAllCurrentEvents )
488 {
489     OSX_SALDATA_RUNINMAIN_UNION( DoYield( false, bHandleAllCurrentEvents), boolean )
490     assert( false && "Don't call this from the main thread!" );
491     return false;
492 
493 }
494 
isWakeupEvent(NSEvent * pEvent)495 static bool isWakeupEvent( NSEvent *pEvent )
496 {
497 SAL_WNODEPRECATED_DECLARATIONS_PUSH
498     return NSApplicationDefined == [pEvent type]
499         && AquaSalInstance::YieldWakeupEvent == static_cast<int>([pEvent subtype]);
500 SAL_WNODEPRECATED_DECLARATIONS_POP
501 }
502 
DoYield(bool bWait,bool bHandleAllCurrentEvents)503 bool AquaSalInstance::DoYield(bool bWait, bool bHandleAllCurrentEvents)
504 {
505     // ensure that the per thread autorelease pool is top level and
506     // will therefore not be destroyed by cocoa implicitly
507     SalData::ensureThreadAutoreleasePool();
508 
509     // NSAutoreleasePool documentation suggests we should have
510     // an own pool for each yield level
511     ReleasePoolHolder aReleasePool;
512 
513     // first, process current user events
514     bool bHadEvent = DispatchUserEvents( bHandleAllCurrentEvents );
515     if ( !bHandleAllCurrentEvents && bHadEvent )
516         return true;
517 
518     // handle cocoa event queue
519     // cocoa events may be only handled in the thread the NSApp was created
520     if( IsMainThread() && mnActivePrintJobs == 0 )
521     {
522         // handle available events
523         NSEvent* pEvent = nil;
524         NSTimeInterval now = [[NSProcessInfo processInfo] systemUptime];
525         mbTimerProcessed = false;
526 
527         do
528         {
529             SolarMutexReleaser aReleaser;
530 
531             pEvent = [NSApp nextEventMatchingMask: NSEventMaskAny
532                             untilDate: nil
533                             inMode: NSDefaultRunLoopMode
534                             dequeue: YES];
535             if( pEvent )
536             {
537                 [NSApp sendEvent: pEvent];
538                 if ( isWakeupEvent( pEvent ) )
539                     continue;
540                 bHadEvent = true;
541             }
542 
543             [NSApp updateWindows];
544 
545             if ( !bHandleAllCurrentEvents || !pEvent || now < [pEvent timestamp] )
546                 break;
547         }
548         while( true );
549 
550         AquaSalTimer *pTimer = static_cast<AquaSalTimer*>( ImplGetSVData()->maSchedCtx.mpSalTimer );
551         if ( !mbTimerProcessed && pTimer && pTimer->IsDirectTimeout() )
552         {
553             pTimer->handleTimerElapsed();
554             bHadEvent = true;
555         }
556 
557         // if we had no event yet, wait for one if requested
558         if( bWait && ! bHadEvent )
559         {
560             SolarMutexReleaser aReleaser;
561 
562             pEvent = [NSApp nextEventMatchingMask: NSEventMaskAny
563                             untilDate: [NSDate distantFuture]
564                             inMode: NSDefaultRunLoopMode
565                             dequeue: YES];
566             if( pEvent )
567             {
568                 [NSApp sendEvent: pEvent];
569                 if ( !isWakeupEvent( pEvent ) )
570                     bHadEvent = true;
571             }
572             [NSApp updateWindows];
573         }
574 
575         // collect update rectangles
576         for( auto pSalFrame : GetSalData()->mpInstance->getFrames() )
577         {
578             AquaSalFrame* pFrame = static_cast<AquaSalFrame*>( pSalFrame );
579             if( pFrame->mbShown && ! pFrame->maInvalidRect.IsEmpty() )
580             {
581                 pFrame->Flush( pFrame->maInvalidRect );
582                 pFrame->maInvalidRect.SetEmpty();
583             }
584         }
585 
586         if ( bHadEvent )
587             maWaitingYieldCond.set();
588     }
589     else
590     {
591         bHadEvent = RunInMainYield( bHandleAllCurrentEvents );
592         if ( !bHadEvent && bWait )
593         {
594             // #i103162#
595             // wait until the main thread has dispatched an event
596             maWaitingYieldCond.reset();
597             SolarMutexReleaser aReleaser;
598             maWaitingYieldCond.wait();
599         }
600     }
601 
602     // we get some apple events way too early
603     // before the application is ready to handle them,
604     // so their corresponding application events need to be delayed
605     // now is a good time to handle at least one of them
606     if( bWait && !aAppEventList.empty() && ImplGetSVData()->maAppData.mbInAppExecute )
607     {
608         // make sure that only one application event is active at a time
609         static bool bInAppEvent = false;
610         if( !bInAppEvent )
611         {
612             bInAppEvent = true;
613             // get the next delayed application event
614             const ApplicationEvent* pAppEvent = aAppEventList.front();
615             aAppEventList.pop_front();
616             // handle one application event (no recursion)
617             const ImplSVData* pSVData = ImplGetSVData();
618             pSVData->mpApp->AppEvent( *pAppEvent );
619             delete pAppEvent;
620             // allow the next delayed application event
621             bInAppEvent = false;
622         }
623     }
624 
625     return bHadEvent;
626 }
627 
AnyInput(VclInputFlags nType)628 bool AquaSalInstance::AnyInput( VclInputFlags nType )
629 {
630     if( nType & VclInputFlags::APPEVENT )
631     {
632         if( ! aAppEventList.empty() )
633             return true;
634         if( nType == VclInputFlags::APPEVENT )
635             return false;
636     }
637 
638     OSX_INST_RUNINMAIN_UNION( AnyInput( nType ), boolean )
639 
640     if( nType & VclInputFlags::TIMER )
641     {
642         AquaSalTimer *pTimer = static_cast<AquaSalTimer*>( ImplGetSVData()->maSchedCtx.mpSalTimer );
643         if (pTimer && pTimer->IsTimerElapsed())
644             return true;
645     }
646 
647     unsigned/*NSUInteger*/ nEventMask = 0;
648 SAL_WNODEPRECATED_DECLARATIONS_PUSH
649         // 'NSFlagsChangedMask' is deprecated: first deprecated in macOS 10.12
650         // 'NSKeyDownMask' is deprecated: first deprecated in macOS 10.12
651         // 'NSKeyUpMask' is deprecated: first deprecated in macOS 10.12
652         // 'NSLeftMouseDownMask' is deprecated: first deprecated in macOS 10.12
653         // 'NSLeftMouseDraggedMask' is deprecated: first deprecated in macOS 10.12
654         // 'NSLeftMouseUpMask' is deprecated: first deprecated in macOS 10.12
655         // 'NSMouseEnteredMask' is deprecated: first deprecated in macOS 10.12
656         // 'NSMouseExitedMask' is deprecated: first deprecated in macOS 10.12
657         // 'NSOtherMouseDownMask' is deprecated: first deprecated in macOS 10.12
658         // 'NSOtherMouseDraggedMask' is deprecated: first deprecated in macOS 10.12
659         // 'NSOtherMouseUpMask' is deprecated: first deprecated in macOS 10.12
660         // 'NSRightMouseDownMask' is deprecated: first deprecated in macOS 10.12
661         // 'NSRightMouseDraggedMask' is deprecated: first deprecated in macOS 10.12
662         // 'NSRightMouseUpMask' is deprecated: first deprecated in macOS 10.12
663         // 'NSScrollWheelMask' is deprecated: first deprecated in macOS 10.12
664         // 'NSTabletPoint' is deprecated: first deprecated in macOS 10.12
665     if( nType & VclInputFlags::MOUSE)
666         nEventMask |=
667             NSLeftMouseDownMask    | NSRightMouseDownMask    | NSOtherMouseDownMask |
668             NSLeftMouseUpMask      | NSRightMouseUpMask      | NSOtherMouseUpMask |
669             NSLeftMouseDraggedMask | NSRightMouseDraggedMask | NSOtherMouseDraggedMask |
670             NSScrollWheelMask |
671             // NSMouseMovedMask |
672             NSMouseEnteredMask | NSMouseExitedMask;
673     if( nType & VclInputFlags::KEYBOARD)
674         nEventMask |= NSKeyDownMask | NSKeyUpMask | NSFlagsChangedMask;
675     if( nType & VclInputFlags::OTHER)
676         nEventMask |= NSTabletPoint | NSApplicationDefinedMask;
677 SAL_WNODEPRECATED_DECLARATIONS_POP
678     // TODO: VclInputFlags::PAINT / more VclInputFlags::OTHER
679     if( !bool(nType) )
680         return false;
681 
682     NSEvent* pEvent = [NSApp nextEventMatchingMask: nEventMask untilDate: nil
683                             inMode: NSDefaultRunLoopMode dequeue: NO];
684     return (pEvent != nullptr);
685 }
686 
CreateChildFrame(SystemParentData *,SalFrameStyleFlags)687 SalFrame* AquaSalInstance::CreateChildFrame( SystemParentData*, SalFrameStyleFlags /*nSalFrameStyle*/ )
688 {
689     return nullptr;
690 }
691 
CreateFrame(SalFrame * pParent,SalFrameStyleFlags nSalFrameStyle)692 SalFrame* AquaSalInstance::CreateFrame( SalFrame* pParent, SalFrameStyleFlags nSalFrameStyle )
693 {
694     OSX_INST_RUNINMAIN_POINTER( CreateFrame( pParent, nSalFrameStyle ), SalFrame* )
695     return new AquaSalFrame( pParent, nSalFrameStyle );
696 }
697 
DestroyFrame(SalFrame * pFrame)698 void AquaSalInstance::DestroyFrame( SalFrame* pFrame )
699 {
700     OSX_INST_RUNINMAIN( DestroyFrame( pFrame ) )
701     delete pFrame;
702 }
703 
CreateObject(SalFrame * pParent,SystemWindowData * pWindowData,bool)704 SalObject* AquaSalInstance::CreateObject( SalFrame* pParent, SystemWindowData* pWindowData, bool /* bShow */ )
705 {
706     if ( !pParent )
707         return nullptr;
708 
709     OSX_INST_RUNINMAIN_POINTER( CreateObject( pParent, pWindowData, false ), SalObject* )
710     return new AquaSalObject( static_cast<AquaSalFrame*>(pParent), pWindowData );
711 }
712 
DestroyObject(SalObject * pObject)713 void AquaSalInstance::DestroyObject( SalObject* pObject )
714 {
715     OSX_INST_RUNINMAIN( DestroyObject( pObject ) )
716     delete pObject;
717 }
718 
CreatePrinter(SalInfoPrinter * pInfoPrinter)719 std::unique_ptr<SalPrinter> AquaSalInstance::CreatePrinter( SalInfoPrinter* pInfoPrinter )
720 {
721     return std::unique_ptr<SalPrinter>(new AquaSalPrinter( dynamic_cast<AquaSalInfoPrinter*>(pInfoPrinter) ));
722 }
723 
GetPrinterQueueInfo(ImplPrnQueueList * pList)724 void AquaSalInstance::GetPrinterQueueInfo( ImplPrnQueueList* pList )
725 {
726     NSArray* pNames = [NSPrinter printerNames];
727     NSArray* pTypes = [NSPrinter printerTypes];
728     unsigned int nNameCount = pNames ? [pNames count] : 0;
729     unsigned int nTypeCount = pTypes ? [pTypes count] : 0;
730     SAL_WARN_IF( nTypeCount != nNameCount, "vcl", "type count not equal to printer count" );
731     for( unsigned int i = 0; i < nNameCount; i++ )
732     {
733         NSString* pName = [pNames objectAtIndex: i];
734         NSString* pType = i < nTypeCount ? [pTypes objectAtIndex: i] : nil;
735         if( pName )
736         {
737             std::unique_ptr<SalPrinterQueueInfo> pInfo(new SalPrinterQueueInfo);
738             pInfo->maPrinterName    = GetOUString( pName );
739             if( pType )
740                 pInfo->maDriver     = GetOUString( pType );
741             pInfo->mnStatus         = PrintQueueFlags::NONE;
742             pInfo->mnJobs           = 0;
743 
744             pList->Add( std::move(pInfo) );
745         }
746     }
747 }
748 
GetPrinterQueueState(SalPrinterQueueInfo *)749 void AquaSalInstance::GetPrinterQueueState( SalPrinterQueueInfo* )
750 {
751 }
752 
GetDefaultPrinter()753 OUString AquaSalInstance::GetDefaultPrinter()
754 {
755     // #i113170# may not be the main thread if called from UNO API
756     SalData::ensureThreadAutoreleasePool();
757 
758     if( maDefaultPrinter.isEmpty() )
759     {
760         NSPrintInfo* pPI = [NSPrintInfo sharedPrintInfo];
761         SAL_WARN_IF( !pPI, "vcl", "no print info" );
762         if( pPI )
763         {
764             NSPrinter* pPr = [pPI printer];
765             SAL_WARN_IF( !pPr, "vcl", "no printer in default info" );
766             if( pPr )
767             {
768                 NSString* pDefName = [pPr name];
769                 SAL_WARN_IF( !pDefName, "vcl", "printer has no name" );
770                 maDefaultPrinter = GetOUString( pDefName );
771             }
772         }
773     }
774     return maDefaultPrinter;
775 }
776 
CreateInfoPrinter(SalPrinterQueueInfo * pQueueInfo,ImplJobSetup * pSetupData)777 SalInfoPrinter* AquaSalInstance::CreateInfoPrinter( SalPrinterQueueInfo* pQueueInfo,
778                                                     ImplJobSetup* pSetupData )
779 {
780     // #i113170# may not be the main thread if called from UNO API
781     SalData::ensureThreadAutoreleasePool();
782 
783     SalInfoPrinter* pNewInfoPrinter = nullptr;
784     if( pQueueInfo )
785     {
786         pNewInfoPrinter = new AquaSalInfoPrinter( *pQueueInfo );
787         if( pSetupData )
788             pNewInfoPrinter->SetPrinterData( pSetupData );
789     }
790 
791     return pNewInfoPrinter;
792 }
793 
DestroyInfoPrinter(SalInfoPrinter * pPrinter)794 void AquaSalInstance::DestroyInfoPrinter( SalInfoPrinter* pPrinter )
795 {
796     // #i113170# may not be the main thread if called from UNO API
797     SalData::ensureThreadAutoreleasePool();
798 
799     delete pPrinter;
800 }
801 
GetConnectionIdentifier()802 OUString AquaSalInstance::GetConnectionIdentifier()
803 {
804     return OUString();
805 }
806 
807 // We need to re-encode file urls because osl_getFileURLFromSystemPath converts
808 // to UTF-8 before encoding non ascii characters, which is not what other apps expect.
translateToExternalUrl(const OUString & internalUrl)809 static OUString translateToExternalUrl(const OUString& internalUrl)
810 {
811     uno::Reference< uno::XComponentContext > context(
812         comphelper::getProcessComponentContext());
813     return uri::ExternalUriReferenceTranslator::create(context)->translateToExternal(internalUrl);
814 }
815 
816 // #i104525# many versions of OSX have problems with some URLs:
817 // when an app requests OSX to add one of these URLs to the "Recent Items" list
818 // then this app gets killed (TextEdit, Preview, etc. and also OOo)
isDangerousUrl(const OUString & rUrl)819 static bool isDangerousUrl( const OUString& rUrl )
820 {
821     // use a heuristic that detects all known cases since there is no official comment
822     // on the exact impact and root cause of the OSX bug
823     const int nLen = rUrl.getLength();
824     const sal_Unicode* p = rUrl.getStr();
825     for( int i = 0; i < nLen-3; ++i, ++p ) {
826         if( p[0] != '%' )
827             continue;
828         // escaped percent?
829         if( (p[1] == '2') && (p[2] == '5') )
830             return true;
831         // escapes are considered to be UTF-8 encoded
832         // => check for invalid UTF-8 leading byte
833         if( (p[1] != 'f') && (p[1] != 'F') )
834             continue;
835         int cLowNibble = p[2];
836         if( (cLowNibble >= '0' ) && (cLowNibble <= '9'))
837             return false;
838         if( cLowNibble >= 'a' )
839             cLowNibble -= 'a' - 'A';
840         if( (cLowNibble < 'A') || (cLowNibble >= 'C'))
841             return true;
842     }
843 
844     return false;
845 }
846 
AddToRecentDocumentList(const OUString & rFileUrl,const OUString &,const OUString &)847 void AquaSalInstance::AddToRecentDocumentList(const OUString& rFileUrl, const OUString& /*rMimeType*/, const OUString& /*rDocumentService*/)
848 {
849     // Convert file URL for external use (see above)
850     OUString externalUrl = translateToExternalUrl(rFileUrl);
851     if( externalUrl.isEmpty() )
852         externalUrl = rFileUrl;
853 
854     if( !externalUrl.isEmpty() && !isDangerousUrl( externalUrl ) )
855     {
856         NSString* pString = CreateNSString( externalUrl );
857         NSURL* pURL = [NSURL URLWithString: pString];
858 
859         if( pURL )
860         {
861             NSDocumentController* pCtrl = [NSDocumentController sharedDocumentController];
862             [pCtrl noteNewRecentDocumentURL: pURL];
863         }
864         if( pString )
865             [pString release];
866     }
867 }
868 
CreateSalTimer()869 SalTimer* AquaSalInstance::CreateSalTimer()
870 {
871     return new AquaSalTimer();
872 }
873 
CreateSalSystem()874 SalSystem* AquaSalInstance::CreateSalSystem()
875 {
876     return new AquaSalSystem();
877 }
878 
CreateSalBitmap()879 std::shared_ptr<SalBitmap> AquaSalInstance::CreateSalBitmap()
880 {
881     return std::make_shared<QuartzSalBitmap>();
882 }
883 
getOSVersion()884 OUString AquaSalInstance::getOSVersion()
885 {
886     NSString * versionString = nullptr;
887     NSDictionary * sysVersionDict = [ NSDictionary dictionaryWithContentsOfFile: @"/System/Library/CoreServices/SystemVersion.plist" ];
888     if ( sysVersionDict )
889         versionString = [ sysVersionDict valueForKey: @"ProductVersion" ];
890 
891     OUString aVersion = "Mac OS X ";
892     if ( versionString )
893         aVersion += OUString::fromUtf8( [ versionString UTF8String ] );
894     else
895         aVersion += "(unknown)";
896 
897     return aVersion;
898 }
899 
CreateCGImage(const Image & rImage)900 CGImageRef CreateCGImage( const Image& rImage )
901 {
902     BitmapEx aBmpEx( rImage.GetBitmapEx() );
903     Bitmap aBmp( aBmpEx.GetBitmap() );
904 
905     if( aBmp.IsEmpty() || ! aBmp.ImplGetSalBitmap() )
906         return nullptr;
907 
908     // simple case, no transparency
909     QuartzSalBitmap* pSalBmp = static_cast<QuartzSalBitmap*>(aBmp.ImplGetSalBitmap().get());
910 
911     if( ! pSalBmp )
912         return nullptr;
913 
914     CGImageRef xImage = nullptr;
915     if( !aBmpEx.IsAlpha() )
916         xImage = pSalBmp->CreateCroppedImage( 0, 0, pSalBmp->mnWidth, pSalBmp->mnHeight );
917     else
918     {
919         AlphaMask aAlphaMask( aBmpEx.GetAlpha() );
920         Bitmap aMask( aAlphaMask.GetBitmap() );
921         QuartzSalBitmap* pMaskBmp = static_cast<QuartzSalBitmap*>(aMask.ImplGetSalBitmap().get());
922         if( pMaskBmp )
923             xImage = pSalBmp->CreateWithMask( *pMaskBmp, 0, 0, pSalBmp->mnWidth, pSalBmp->mnHeight );
924         else
925             xImage = pSalBmp->CreateCroppedImage( 0, 0, pSalBmp->mnWidth, pSalBmp->mnHeight );
926     }
927 
928     return xImage;
929 }
930 
CreateNSImage(const Image & rImage)931 NSImage* CreateNSImage( const Image& rImage )
932 {
933     CGImageRef xImage = CreateCGImage( rImage );
934 
935     if( ! xImage )
936         return nil;
937 
938     Size aSize( rImage.GetSizePixel() );
939     NSImage* pImage = [[NSImage alloc] initWithSize: NSMakeSize( aSize.Width(), aSize.Height() )];
940     if( pImage )
941     {
942         [pImage lockFocusFlipped:YES];
943         NSGraphicsContext* pContext = [NSGraphicsContext currentContext];
944         CGContextRef rCGContext = [pContext CGContext];
945 
946         const CGRect aDstRect = { {0, 0}, { static_cast<CGFloat>(aSize.Width()), static_cast<CGFloat>(aSize.Height()) } };
947         CGContextDrawImage( rCGContext, aDstRect, xImage );
948 
949         [pImage unlockFocus];
950     }
951 
952     CGImageRelease( xImage );
953 
954     return pImage;
955 }
956 
SVMainHook(int * pnInit)957 bool AquaSalInstance::SVMainHook(int* pnInit)
958 {
959     gpnInit = pnInit;
960 
961     OUString aExeURL, aExe;
962     osl_getExecutableFile( &aExeURL.pData );
963     osl_getSystemPathFromFileURL( aExeURL.pData, &aExe.pData );
964     OString aByteExe( OUStringToOString( aExe, osl_getThreadTextEncoding() ) );
965 
966 #ifdef DEBUG
967     aByteExe += OString ( " NSAccessibilityDebugLogLevel 1" );
968     const char* pArgv[] = { aByteExe.getStr(), NULL };
969     NSApplicationMain( 3, pArgv );
970 #else
971     const char* pArgv[] = { aByteExe.getStr(), nullptr };
972     NSApplicationMain( 1, pArgv );
973 #endif
974 
975     return true;
976 }
977 
978 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
979