xref: /core/vcl/osx/salinst.cxx (revision 92815f3a)
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 #include <strings.hrc>
64 
65 #include <comphelper/processfactory.hxx>
66 
67 #include <com/sun/star/uri/ExternalUriReferenceTranslator.hpp>
68 #include <com/sun/star/uno/XComponentContext.hpp>
69 
70 #include <premac.h>
71 #include <Foundation/Foundation.h>
72 #include <ApplicationServices/ApplicationServices.h>
73 #import "apple_remote/RemoteMainController.h"
74 #include <apple_remote/RemoteControl.h>
75 #include <postmac.h>
76 
77 #if HAVE_FEATURE_SKIA
78 #include <vcl/skia/SkiaHelper.hxx>
79 #include <skia/salbmp.hxx>
80 #include <skia/osx/gdiimpl.hxx>
81 #include <skia/osx/bitmap.hxx>
82 #endif
83 
84 extern "C" {
85 #include <crt_externs.h>
86 }
87 
88 using namespace ::com::sun::star;
89 
90 static int* gpnInit = nullptr;
91 static NSMenu* pDockMenu = nil;
92 static bool bLeftMain = false;
93 
94 namespace {
95 
96 class AquaDelayedSettingsChanged : public Idle
97 {
98     bool            mbInvalidate;
99 
100 public:
AquaDelayedSettingsChanged(bool bInvalidate)101     AquaDelayedSettingsChanged( bool bInvalidate ) :
102         Idle("AquaSalInstance AquaDelayedSettingsChanged"),
103         mbInvalidate( bInvalidate )
104     {
105     }
106 
Invoke()107     virtual void Invoke() override
108     {
109         AquaSalInstance *pInst = GetSalData()->mpInstance;
110         SalFrame *pAnyFrame = pInst->anyFrame();
111         if( pAnyFrame )
112             pAnyFrame->CallCallback( SalEvent::SettingsChanged, nullptr );
113 
114         if( mbInvalidate )
115         {
116             for( auto pSalFrame : pInst->getFrames() )
117             {
118                 AquaSalFrame* pFrame = static_cast<AquaSalFrame*>( pSalFrame );
119                 if( pFrame->mbShown )
120                     pFrame->SendPaintEvent();
121             }
122         }
123         delete this;
124     }
125 };
126 
127 }
128 
getFallbackPrinterName()129 static OUString& getFallbackPrinterName()
130 {
131     static OUString aFallbackPrinter;
132 
133     if ( aFallbackPrinter.isEmpty() )
134     {
135         aFallbackPrinter = VclResId( SV_PRINT_DEFPRT_TXT );
136         if ( aFallbackPrinter.isEmpty() )
137             aFallbackPrinter = "Printer";
138     }
139 
140     return aFallbackPrinter;
141 }
142 
delayedSettingsChanged(bool bInvalidate)143 void AquaSalInstance::delayedSettingsChanged( bool bInvalidate )
144 {
145     osl::Guard< comphelper::SolarMutex > aGuard( *GetYieldMutex() );
146     AquaDelayedSettingsChanged* pIdle = new AquaDelayedSettingsChanged( bInvalidate );
147     pIdle->Start();
148 }
149 
150 // the std::list<const ApplicationEvent*> must be available before any SalData/SalInst/etc. objects are ready
151 std::list<const ApplicationEvent*> AquaSalInstance::aAppEventList;
152 
GetDynamicDockMenu()153 NSMenu* AquaSalInstance::GetDynamicDockMenu()
154 {
155     if( ! pDockMenu && ! bLeftMain )
156         pDockMenu = [[NSMenu alloc] initWithTitle: @""];
157     return pDockMenu;
158 }
159 
isOnCommandLine(const OUString & rArg)160 bool AquaSalInstance::isOnCommandLine( const OUString& rArg )
161 {
162     sal_uInt32 nArgs = osl_getCommandArgCount();
163     for( sal_uInt32 i = 0; i < nArgs; i++ )
164     {
165         OUString aArg;
166         osl_getCommandArg( i, &aArg.pData );
167         if( aArg.equals( rArg ) )
168             return true;
169     }
170     return false;
171 }
172 
AfterAppInit()173 void AquaSalInstance::AfterAppInit()
174 {
175     [[NSNotificationCenter defaultCenter] addObserver: NSApp
176                                           selector: @selector(systemColorsChanged:)
177                                           name: NSSystemColorsDidChangeNotification
178                                           object: nil ];
179     [[NSNotificationCenter defaultCenter] addObserver: NSApp
180                                           selector: @selector(screenParametersChanged:)
181                                           name: NSApplicationDidChangeScreenParametersNotification
182                                           object: nil ];
183     // add observers for some settings changes that affect vcl's settings
184     // scrollbar variant
185     [[NSDistributedNotificationCenter defaultCenter] addObserver: NSApp
186                                           selector: @selector(scrollbarVariantChanged:)
187                                           name: @"AppleAquaScrollBarVariantChanged"
188                                           object: nil ];
189     // scrollbar page behavior ("jump to here" or not)
190     [[NSDistributedNotificationCenter defaultCenter] addObserver: NSApp
191                                           selector: @selector(scrollbarSettingsChanged:)
192                                           name: @"AppleNoRedisplayAppearancePreferenceChanged"
193                                           object: nil ];
194 #if !HAVE_FEATURE_MACOSX_SANDBOX
195     // Initialize Apple Remote
196     GetSalData()->mpAppleRemoteMainController = [[AppleRemoteMainController alloc] init];
197 
198     [[NSDistributedNotificationCenter defaultCenter] addObserver: NSApp
199                                            selector: @selector(applicationWillBecomeActive:)
200                                            name: @"AppleRemoteWillBecomeActive"
201                                            object: nil ];
202 
203     [[NSDistributedNotificationCenter defaultCenter] addObserver: NSApp
204                                            selector: @selector(applicationWillResignActive:)
205                                            name: @"AppleRemoteWillResignActive"
206                                            object: nil ];
207 #endif
208 
209     // HACK: When the first call to [NSSpellChecker sharedSpellChecker] (in
210     // lingucomponent/source/spellcheck/macosxspell/macspellimp.mm) is done both on a thread other
211     // than the main thread and with the SolarMutex erroneously locked, then that can lead to
212     // deadlock as [NSSpellChecker sharedSpellChecker] internally calls
213     //   AppKit`-[NSSpellChecker init] ->
214     //   AppKit`-[NSSpellChecker _fillSpellCheckerPopupButton:] ->
215     //   AppKit`-[NSApplication(NSServicesMenuPrivate) _fillSpellCheckerPopupButton:] ->
216     //   AppKit`-[NSMenu insertItem:atIndex:] ->
217     //   Foundation`-[NSNotificationCenter postNotificationName:object:userInfo:] ->
218     //   CoreFoundation`_CFXNotificationPost ->
219     //   Foundation`-[NSOperation waitUntilFinished]
220     // waiting for work to be done on the main thread, but the main thread is typically already
221     // blocked (in some event handling loop) waiting to acquire the SolarMutex.  The real solution
222     // would be to fix all the cases where a call to [NSSpellChecker sharedSpellChecker] in
223     // lingucomponent/source/spellcheck/macosxspell/macspellimp.mm is done while the SolarMutex is
224     // locked (somewhere up the call chain), but that appears to be rather difficult (see e.g.
225     // <https://bugs.documentfoundation.org/show_bug.cgi?id=151894> "FILEOPEN a Base Document with
226     // customized event for open a startform by 'open document' LO stuck").  So, at least for now,
227     // chicken out and do that first call to [NSSpellChecker sharedSpellChecker] upfront in a
228     // controlled environment:
229     [NSSpellChecker sharedSpellChecker];
230 }
231 
SalYieldMutex()232 SalYieldMutex::SalYieldMutex()
233     : m_aCodeBlock( nullptr )
234 {
235 }
236 
~SalYieldMutex()237 SalYieldMutex::~SalYieldMutex()
238 {
239 }
240 
doAcquire(sal_uInt32 nLockCount)241 void SalYieldMutex::doAcquire( sal_uInt32 nLockCount )
242 {
243     AquaSalInstance *pInst = GetSalData()->mpInstance;
244     if ( pInst && pInst->IsMainThread() )
245     {
246         if ( pInst->mbNoYieldLock )
247             return;
248         do {
249             RuninmainBlock block = nullptr;
250             {
251                 std::unique_lock<std::mutex> g(m_runInMainMutex);
252                 if (m_aMutex.tryToAcquire()) {
253                     assert(m_aCodeBlock == nullptr);
254                     m_wakeUpMain = false;
255                     break;
256                 }
257                 // wait for doRelease() or RUNINMAIN_* to set the condition
258                 m_aInMainCondition.wait(g, [this]() { return m_wakeUpMain; });
259                 m_wakeUpMain = false;
260                 std::swap(block, m_aCodeBlock);
261             }
262             if ( block )
263             {
264                 assert( !pInst->mbNoYieldLock );
265                 pInst->mbNoYieldLock = true;
266                 block();
267                 pInst->mbNoYieldLock = false;
268                 Block_release( block );
269                 std::scoped_lock<std::mutex> g(m_runInMainMutex);
270                 assert(!m_resultReady);
271                 m_resultReady = true;
272                 m_aResultCondition.notify_all();
273             }
274         }
275         while ( true );
276     }
277     else
278         m_aMutex.acquire();
279     ++m_nCount;
280     --nLockCount;
281 
282     comphelper::SolarMutex::doAcquire( nLockCount );
283 }
284 
doRelease(const bool bUnlockAll)285 sal_uInt32 SalYieldMutex::doRelease( const bool bUnlockAll )
286 {
287     AquaSalInstance *pInst = GetSalData()->mpInstance;
288     if ( pInst->mbNoYieldLock && pInst->IsMainThread() )
289         return 1;
290     sal_uInt32 nCount;
291     {
292         std::scoped_lock<std::mutex> g(m_runInMainMutex);
293         // read m_nCount before doRelease
294         bool const isReleased(bUnlockAll || m_nCount == 1);
295         nCount = comphelper::SolarMutex::doRelease( bUnlockAll );
296         if (isReleased && !pInst->IsMainThread()) {
297             m_wakeUpMain = true;
298             m_aInMainCondition.notify_all();
299         }
300     }
301     return nCount;
302 }
303 
IsCurrentThread() const304 bool SalYieldMutex::IsCurrentThread() const
305 {
306     if ( !GetSalData()->mpInstance->mbNoYieldLock )
307         return comphelper::SolarMutex::IsCurrentThread();
308     else
309         return GetSalData()->mpInstance->IsMainThread();
310 }
311 
312 // some convenience functions regarding the yield mutex, aka solar mutex
313 
ImplSalYieldMutexTryToAcquire()314 bool ImplSalYieldMutexTryToAcquire()
315 {
316     AquaSalInstance* pInst = GetSalData()->mpInstance;
317     if ( pInst )
318         return pInst->GetYieldMutex()->tryToAcquire();
319     else
320         return false;
321 }
322 
ImplSalYieldMutexRelease()323 void ImplSalYieldMutexRelease()
324 {
325     AquaSalInstance* pInst = GetSalData()->mpInstance;
326     if ( pInst )
327         pInst->GetYieldMutex()->release();
328 }
329 
330 extern "C" {
create_SalInstance()331 VCLPLUG_OSX_PUBLIC SalInstance* create_SalInstance()
332 {
333     SalData* pSalData = new SalData;
334 
335     NSAutoreleasePool * pool = [ [ NSAutoreleasePool alloc ] init ];
336     unlink([[NSString stringWithFormat:@"%@/Library/Saved Application State/%s.savedState/restorecount.plist", NSHomeDirectory(), MACOSX_BUNDLE_IDENTIFIER] UTF8String]);
337     unlink([[NSString stringWithFormat:@"%@/Library/Saved Application State/%s.savedState/restorecount.txt", NSHomeDirectory(), MACOSX_BUNDLE_IDENTIFIER] UTF8String]);
338     [ pool drain ];
339 
340     // create our cocoa NSApplication
341     [VCL_NSApplication sharedApplication];
342 
343     SalData::ensureThreadAutoreleasePool();
344 
345     // put cocoa into multithreaded mode
346     [NSThread detachNewThreadSelector:@selector(enableCocoaThreads:) toTarget:[[CocoaThreadEnabler alloc] init] withObject:nil];
347 
348     // activate our delegate methods
349     [NSApp setDelegate: NSApp];
350 
351     SAL_WARN_IF( pSalData->mpInstance != nullptr, "vcl", "more than one instance created" );
352     AquaSalInstance* pInst = new AquaSalInstance;
353 
354     // init instance (only one instance in this version !!!)
355     pSalData->mpInstance = pInst;
356     // this one is for outside AquaSalInstance::Yield
357     SalData::ensureThreadAutoreleasePool();
358     // no focus rects on NWF
359     ImplGetSVData()->maNWFData.mbNoFocusRects = true;
360     ImplGetSVData()->maNWFData.mbNoActiveTabTextRaise = true;
361     ImplGetSVData()->maNWFData.mbCenteredTabs = true;
362     ImplGetSVData()->maNWFData.mnStatusBarLowerRightOffset = 10;
363 
364     return pInst;
365 }
366 }
367 
AquaSalInstance()368 AquaSalInstance::AquaSalInstance()
369     : SalInstance(std::make_unique<SalYieldMutex>())
370     , mnActivePrintJobs( 0 )
371     , mbNoYieldLock( false )
372     , mbTimerProcessed( false )
373 {
374     maMainThread = osl::Thread::getCurrentIdentifier();
375 
376     ImplSVData* pSVData = ImplGetSVData();
377     pSVData->maAppData.mxToolkitName = OUString("osx");
378     m_bSupportsOpenGL = true;
379 
380     mpButtonCell = [[NSButtonCell alloc] init];
381     mpCheckCell = [[NSButtonCell alloc] init];
382     mpRadioCell = [[NSButtonCell alloc] init];
383     mpTextFieldCell = [[NSTextFieldCell alloc] initTextCell:@""];
384     mpComboBoxCell = [[NSComboBoxCell alloc] initTextCell:@""];
385     mpPopUpButtonCell = [[NSPopUpButtonCell alloc] init];
386     mpStepperCell = [[NSStepperCell alloc] init];
387     mpListNodeCell = [[NSButtonCell alloc] init];
388 
389 #if HAVE_FEATURE_SKIA
390     AquaSkiaSalGraphicsImpl::prepareSkia();
391 #endif
392 }
393 
~AquaSalInstance()394 AquaSalInstance::~AquaSalInstance()
395 {
396     [NSApp stop: NSApp];
397     bLeftMain = true;
398     if( pDockMenu )
399     {
400         [pDockMenu release];
401         pDockMenu = nil;
402     }
403 
404     [mpListNodeCell release];
405     [mpStepperCell release];
406     [mpPopUpButtonCell release];
407     [mpComboBoxCell release];
408     [mpTextFieldCell release];
409     [mpRadioCell release];
410     [mpCheckCell release];
411     [mpButtonCell release];
412 
413 #if HAVE_FEATURE_SKIA
414     SkiaHelper::cleanup();
415 #endif
416 }
417 
TriggerUserEventProcessing()418 void AquaSalInstance::TriggerUserEventProcessing()
419 {
420     dispatch_async(dispatch_get_main_queue(),^{
421         ImplNSAppPostEvent( AquaSalInstance::YieldWakeupEvent, NO );
422     });
423 }
424 
ProcessEvent(SalUserEvent aEvent)425 void AquaSalInstance::ProcessEvent( SalUserEvent aEvent )
426 {
427     aEvent.m_pFrame->CallCallback( aEvent.m_nEvent, aEvent.m_pData );
428     maWaitingYieldCond.set();
429 }
430 
IsMainThread() const431 bool AquaSalInstance::IsMainThread() const
432 {
433     return osl::Thread::getCurrentIdentifier() == maMainThread;
434 }
435 
handleAppDefinedEvent(NSEvent * pEvent)436 void AquaSalInstance::handleAppDefinedEvent( NSEvent* pEvent )
437 {
438     AquaSalTimer *pTimer = static_cast<AquaSalTimer*>( ImplGetSVData()->maSchedCtx.mpSalTimer );
439     int nSubtype = [pEvent subtype];
440     switch( nSubtype )
441     {
442     case AppStartTimerEvent:
443         if ( pTimer )
444             pTimer->handleStartTimerEvent( pEvent );
445         break;
446     case AppExecuteSVMain:
447     {
448         int nRet = ImplSVMain();
449         if (gpnInit)
450             *gpnInit = nRet;
451         [NSApp stop: NSApp];
452         break;
453     }
454     case DispatchTimerEvent:
455     {
456         AquaSalInstance *pInst = GetSalData()->mpInstance;
457         if ( pTimer && pInst )
458             pInst->mbTimerProcessed = pTimer->handleDispatchTimerEvent( pEvent );
459         break;
460     }
461 #if !HAVE_FEATURE_MACOSX_SANDBOX
462     case AppleRemoteControlEvent: // Defined in <apple_remote/RemoteMainController.h>
463     {
464         MediaCommand nCommand;
465         AquaSalInstance *pInst = GetSalData()->mpInstance;
466         bool bIsFullScreenMode = false;
467 
468         for( auto pSalFrame : pInst->getFrames() )
469         {
470             const AquaSalFrame* pFrame = static_cast<const AquaSalFrame*>( pSalFrame );
471             if ( pFrame->mbFullScreen )
472             {
473                 bIsFullScreenMode = true;
474                 break;
475             }
476         }
477 
478         switch ([pEvent data1])
479         {
480             case kRemoteButtonPlay:
481                 nCommand = bIsFullScreenMode ? MediaCommand::PlayPause : MediaCommand::Play;
482                 break;
483 
484             // kept for experimentation purpose (scheduled for future implementation)
485             // case kRemoteButtonMenu:         nCommand = MediaCommand::Menu; break;
486 
487             case kRemoteButtonPlus:         nCommand = MediaCommand::VolumeUp; break;
488 
489             case kRemoteButtonMinus:        nCommand = MediaCommand::VolumeDown; break;
490 
491             case kRemoteButtonRight:        nCommand = MediaCommand::NextTrack; break;
492 
493             case kRemoteButtonRight_Hold:   nCommand = MediaCommand::NextTrackHold; break;
494 
495             case kRemoteButtonLeft:         nCommand = MediaCommand::PreviousTrack; break;
496 
497             case kRemoteButtonLeft_Hold:    nCommand = MediaCommand::Rewind; break;
498 
499             case kRemoteButtonPlay_Hold:    nCommand = MediaCommand::PlayHold; break;
500 
501             case kRemoteButtonMenu_Hold:    nCommand = MediaCommand::Stop; break;
502 
503             // FIXME : not detected
504             case kRemoteButtonPlus_Hold:
505             case kRemoteButtonMinus_Hold:
506                 break;
507 
508             default:
509                 break;
510         }
511         AquaSalFrame* pFrame = static_cast<AquaSalFrame*>( pInst->anyFrame() );
512         vcl::Window* pWindow = pFrame ? pFrame->GetWindow() : nullptr;
513         if( pWindow )
514         {
515             const Point aPoint;
516             CommandMediaData aMediaData(nCommand);
517             CommandEvent aCEvt( aPoint, CommandEventId::Media, false, &aMediaData );
518             NotifyEvent aNCmdEvt( NotifyEventType::COMMAND, pWindow, &aCEvt );
519 
520             if ( !ImplCallPreNotify( aNCmdEvt ) )
521                 pWindow->Command( aCEvt );
522         }
523 
524     }
525     break;
526 #endif
527 
528     case YieldWakeupEvent:
529         // do nothing, fall out of Yield
530         break;
531 
532     default:
533         OSL_FAIL( "unhandled NSEventTypeApplicationDefined event" );
534         break;
535     }
536 }
537 
RunInMainYield(bool bHandleAllCurrentEvents)538 bool AquaSalInstance::RunInMainYield( bool bHandleAllCurrentEvents )
539 {
540     OSX_SALDATA_RUNINMAIN_UNION( DoYield( false, bHandleAllCurrentEvents), boolean )
541 
542     // PrinterController::removeTransparencies() calls this frequently on the
543     // main thread so reduce the severity from an assert so that printing still
544     // works in a debug builds
545     SAL_WARN_IF( true, "vcl", "Don't call this from the main thread!" );
546     return false;
547 
548 }
549 
isWakeupEvent(NSEvent * pEvent)550 static bool isWakeupEvent( NSEvent *pEvent )
551 {
552     return NSEventTypeApplicationDefined == [pEvent type]
553         && AquaSalInstance::YieldWakeupEvent == static_cast<int>([pEvent subtype]);
554 }
555 
DoYield(bool bWait,bool bHandleAllCurrentEvents)556 bool AquaSalInstance::DoYield(bool bWait, bool bHandleAllCurrentEvents)
557 {
558     // ensure that the per thread autorelease pool is top level and
559     // will therefore not be destroyed by cocoa implicitly
560     SalData::ensureThreadAutoreleasePool();
561 
562     // NSAutoreleasePool documentation suggests we should have
563     // an own pool for each yield level
564     ReleasePoolHolder aReleasePool;
565 
566     // first, process current user events
567     // Related: tdf#152703 Eliminate potential blocking during live resize
568     // Only native events and timers need to be dispatched to redraw
569     // the window so skip dispatching user events when a window is in
570     // live resize
571     bool bHadEvent = ( !ImplGetSVData()->mpWinData->mbIsLiveResize && DispatchUserEvents( bHandleAllCurrentEvents ) );
572     if ( !bHandleAllCurrentEvents && bHadEvent )
573         return true;
574 
575     // handle cocoa event queue
576     // cocoa events may be only handled in the thread the NSApp was created
577     if( IsMainThread() && mnActivePrintJobs == 0 )
578     {
579         // handle available events
580         NSEvent* pEvent = nil;
581         NSTimeInterval now = [[NSProcessInfo processInfo] systemUptime];
582         mbTimerProcessed = false;
583 
584         int noLoops = 0;
585         do
586         {
587             SolarMutexReleaser aReleaser;
588 
589             pEvent = [NSApp nextEventMatchingMask: NSEventMaskAny
590                             untilDate: [NSDate distantPast]
591                             inMode: NSDefaultRunLoopMode
592                             dequeue: YES];
593             if( pEvent )
594             {
595                 // tdf#155092 don't dispatch left mouse up events during live resizing
596                 // If this is a left mouse up event, dispatching this event
597                 // will trigger tdf#155092 to occur in the next mouse down
598                 // event. So do not dispatch this event and push it back onto
599                 // the front of the event queue so no more events will be
600                 // dispatched until live resizing ends. Surprisingly, live
601                 // resizing appears to end in the next mouse down event.
602                 if ( ImplGetSVData()->mpWinData->mbIsLiveResize && [pEvent type] == NSEventTypeLeftMouseUp )
603                 {
604                     [NSApp postEvent: pEvent atStart: YES];
605                     return false;
606                 }
607 
608                 [NSApp sendEvent: pEvent];
609                 if ( isWakeupEvent( pEvent ) )
610                     continue;
611                 bHadEvent = true;
612             }
613 
614             [NSApp updateWindows];
615 
616             if ( !bHandleAllCurrentEvents || !pEvent || now < [pEvent timestamp] )
617                 break;
618             // noelgrandin: I see sporadic hangs on the macos jenkins boxes, and the backtrace
619             // points to the this loop - let us see if breaking out of here after too many
620             // trips around helps.
621             noLoops++;
622             if (noLoops == 100)
623                 break;
624         }
625         while( true );
626 
627         AquaSalTimer *pTimer = static_cast<AquaSalTimer*>( ImplGetSVData()->maSchedCtx.mpSalTimer );
628         if ( !mbTimerProcessed && pTimer && pTimer->IsDirectTimeout() )
629         {
630             pTimer->handleTimerElapsed();
631             bHadEvent = true;
632         }
633 
634         // if we had no event yet, wait for one if requested
635         // Related: tdf#152703 Eliminate potential blocking during live resize
636         // Some events and timers call Application::Reschedule() or
637         // Application::Yield() so don't block and wait for events when a
638         // window is in live resize
639         if( bWait && ! bHadEvent && !ImplGetSVData()->mpWinData->mbIsLiveResize )
640         {
641             SolarMutexReleaser aReleaser;
642 
643             // attempt to fix macos jenkins hangs - part 3
644             // oox::xls::WorkbookFragment::finalizeImport() calls
645             // AquaSalInstance::DoYield() with bWait set to true. But
646             // since unit tests generally have no expected user generated
647             // events, we can end up blocking and waiting forever so
648             // don't block and wait when running unit tests.
649             pEvent = [NSApp nextEventMatchingMask: NSEventMaskAny
650                             untilDate: SalInstance::IsRunningUnitTest() ? [NSDate distantPast] : [NSDate distantFuture]
651                             inMode: NSDefaultRunLoopMode
652                             dequeue: YES];
653             if( pEvent )
654             {
655                 [NSApp sendEvent: pEvent];
656                 if ( !isWakeupEvent( pEvent ) )
657                     bHadEvent = true;
658             }
659             [NSApp updateWindows];
660         }
661 
662         // collect update rectangles
663         for( auto pSalFrame : GetSalData()->mpInstance->getFrames() )
664         {
665             AquaSalFrame* pFrame = static_cast<AquaSalFrame*>( pSalFrame );
666             if( pFrame->mbShown && ! pFrame->maInvalidRect.IsEmpty() )
667             {
668                 pFrame->Flush( pFrame->maInvalidRect );
669                 pFrame->maInvalidRect.SetEmpty();
670             }
671         }
672 
673         if ( bHadEvent )
674             maWaitingYieldCond.set();
675     }
676     else
677     {
678         bHadEvent = RunInMainYield( bHandleAllCurrentEvents );
679         if ( !bHadEvent && bWait )
680         {
681             // #i103162#
682             // wait until the main thread has dispatched an event
683             maWaitingYieldCond.reset();
684             SolarMutexReleaser aReleaser;
685             maWaitingYieldCond.wait();
686         }
687     }
688 
689     // we get some apple events way too early
690     // before the application is ready to handle them,
691     // so their corresponding application events need to be delayed
692     // now is a good time to handle at least one of them
693     if( bWait && !aAppEventList.empty() && ImplGetSVData()->maAppData.mbInAppExecute )
694     {
695         // make sure that only one application event is active at a time
696         static bool bInAppEvent = false;
697         if( !bInAppEvent )
698         {
699             bInAppEvent = true;
700             // get the next delayed application event
701             const ApplicationEvent* pAppEvent = aAppEventList.front();
702             aAppEventList.pop_front();
703             // handle one application event (no recursion)
704             const ImplSVData* pSVData = ImplGetSVData();
705             pSVData->mpApp->AppEvent( *pAppEvent );
706             delete pAppEvent;
707             // allow the next delayed application event
708             bInAppEvent = false;
709         }
710     }
711 
712     return bHadEvent;
713 }
714 
AnyInput(VclInputFlags nType)715 bool AquaSalInstance::AnyInput( VclInputFlags nType )
716 {
717     if( nType & VclInputFlags::APPEVENT )
718     {
719         if( ! aAppEventList.empty() )
720             return true;
721         if( nType == VclInputFlags::APPEVENT )
722             return false;
723     }
724 
725     OSX_INST_RUNINMAIN_UNION( AnyInput( nType ), boolean )
726 
727     if( nType & VclInputFlags::TIMER )
728     {
729         AquaSalTimer *pTimer = static_cast<AquaSalTimer*>( ImplGetSVData()->maSchedCtx.mpSalTimer );
730         if (pTimer && pTimer->IsTimerElapsed())
731             return true;
732     }
733 
734     unsigned/*NSUInteger*/ nEventMask = 0;
735     if( nType & VclInputFlags::MOUSE)
736     {
737         nEventMask |=
738             NSEventMaskLeftMouseDown    | NSEventMaskRightMouseDown    | NSEventMaskOtherMouseDown    |
739             NSEventMaskLeftMouseUp      | NSEventMaskRightMouseUp      | NSEventMaskOtherMouseUp      |
740             NSEventMaskLeftMouseDragged | NSEventMaskRightMouseDragged | NSEventMaskOtherMouseDragged |
741             NSEventMaskScrollWheel      |
742             // NSEventMaskMouseMoved    |
743             NSEventMaskMouseEntered     | NSEventMaskMouseExited;
744 
745         // Related: tdf#155266 stop delaying painting timer while swiping
746         // After fixing several flushing issues in tdf#155266, scrollbars
747         // still will not redraw until swiping has ended or paused when
748         // using Skia/Raster or Skia disabled. So, stop the delay by only
749         // including NSEventMaskScrollWheel if the current event type is
750         // not NSEventTypeScrollWheel.
751         NSEvent* pCurrentEvent = [NSApp currentEvent];
752         if( pCurrentEvent && [pCurrentEvent type] == NSEventTypeScrollWheel )
753         {
754             // tdf#160767 skip fix for tdf#155266 when the event hasn't changed
755             // When scrolling in Writer with automatic spellchecking enabled,
756             // the current event never changes because the fix for tdf#155266
757             // causes Writer to get stuck in a loop. So, if the current event
758             // has not changed since the last pass through this code, skip
759             // the fix for tdf#155266.
760             static NSEvent *pLastCurrentEvent = nil;
761             if( pLastCurrentEvent != pCurrentEvent )
762             {
763                 if( pLastCurrentEvent )
764                     [pLastCurrentEvent release];
765                 pLastCurrentEvent = [pCurrentEvent retain];
766                 nEventMask &= ~NSEventMaskScrollWheel;
767             }
768         }
769     }
770 
771     if( nType & VclInputFlags::KEYBOARD)
772         nEventMask |= NSEventMaskKeyDown | NSEventMaskKeyUp | NSEventMaskFlagsChanged;
773     if( nType & VclInputFlags::OTHER)
774         nEventMask |= NSEventMaskTabletPoint | NSEventMaskApplicationDefined;
775     // TODO: VclInputFlags::PAINT / more VclInputFlags::OTHER
776     if( !bool(nType) )
777         return false;
778 
779     NSEvent* pEvent = [NSApp nextEventMatchingMask: nEventMask untilDate: [NSDate distantPast]
780                             inMode: NSDefaultRunLoopMode dequeue: NO];
781     return (pEvent != nullptr);
782 }
783 
CreateChildFrame(SystemParentData *,SalFrameStyleFlags)784 SalFrame* AquaSalInstance::CreateChildFrame( SystemParentData*, SalFrameStyleFlags /*nSalFrameStyle*/ )
785 {
786     return nullptr;
787 }
788 
CreateFrame(SalFrame * pParent,SalFrameStyleFlags nSalFrameStyle)789 SalFrame* AquaSalInstance::CreateFrame( SalFrame* pParent, SalFrameStyleFlags nSalFrameStyle )
790 {
791     OSX_INST_RUNINMAIN_POINTER( CreateFrame( pParent, nSalFrameStyle ), SalFrame* )
792     return new AquaSalFrame( pParent, nSalFrameStyle );
793 }
794 
DestroyFrame(SalFrame * pFrame)795 void AquaSalInstance::DestroyFrame( SalFrame* pFrame )
796 {
797     OSX_INST_RUNINMAIN( DestroyFrame( pFrame ) )
798     delete pFrame;
799 }
800 
CreateObject(SalFrame * pParent,SystemWindowData * pWindowData,bool)801 SalObject* AquaSalInstance::CreateObject( SalFrame* pParent, SystemWindowData* pWindowData, bool /* bShow */ )
802 {
803     if ( !pParent )
804         return nullptr;
805 
806     OSX_INST_RUNINMAIN_POINTER( CreateObject( pParent, pWindowData, false ), SalObject* )
807     return new AquaSalObject( static_cast<AquaSalFrame*>(pParent), pWindowData );
808 }
809 
DestroyObject(SalObject * pObject)810 void AquaSalInstance::DestroyObject( SalObject* pObject )
811 {
812     OSX_INST_RUNINMAIN( DestroyObject( pObject ) )
813     delete pObject;
814 }
815 
CreatePrinter(SalInfoPrinter * pInfoPrinter)816 std::unique_ptr<SalPrinter> AquaSalInstance::CreatePrinter( SalInfoPrinter* pInfoPrinter )
817 {
818     return std::unique_ptr<SalPrinter>(new AquaSalPrinter( dynamic_cast<AquaSalInfoPrinter*>(pInfoPrinter) ));
819 }
820 
GetPrinterQueueInfo(ImplPrnQueueList * pList)821 void AquaSalInstance::GetPrinterQueueInfo( ImplPrnQueueList* pList )
822 {
823     NSArray* pNames = [NSPrinter printerNames];
824     NSArray* pTypes = [NSPrinter printerTypes];
825     unsigned int nNameCount = pNames ? [pNames count] : 0;
826     unsigned int nTypeCount = pTypes ? [pTypes count] : 0;
827     SAL_WARN_IF( nTypeCount != nNameCount, "vcl", "type count not equal to printer count" );
828     for( unsigned int i = 0; i < nNameCount; i++ )
829     {
830         NSString* pName = [pNames objectAtIndex: i];
831         NSString* pType = i < nTypeCount ? [pTypes objectAtIndex: i] : nil;
832         if( pName )
833         {
834             std::unique_ptr<SalPrinterQueueInfo> pInfo(new SalPrinterQueueInfo);
835             pInfo->maPrinterName    = GetOUString( pName );
836             if( pType )
837                 pInfo->maDriver     = GetOUString( pType );
838             pInfo->mnStatus         = PrintQueueFlags::NONE;
839             pInfo->mnJobs           = 0;
840 
841             pList->Add( std::move(pInfo) );
842         }
843     }
844 
845     // tdf#151700 Prevent the non-native LibreOffice PrintDialog from
846     // displaying by creating a fake printer if there are no printers. This
847     // will allow the LibreOffice printing code to proceed with native
848     // NSPrintOperation which will display the native print panel.
849     if ( !nNameCount )
850     {
851         std::unique_ptr<SalPrinterQueueInfo> pInfo(new SalPrinterQueueInfo);
852         pInfo->maPrinterName    = getFallbackPrinterName();
853         pInfo->mnStatus         = PrintQueueFlags::NONE;
854         pInfo->mnJobs           = 0;
855 
856         pList->Add( std::move(pInfo) );
857     }
858 }
859 
GetPrinterQueueState(SalPrinterQueueInfo *)860 void AquaSalInstance::GetPrinterQueueState( SalPrinterQueueInfo* )
861 {
862 }
863 
GetDefaultPrinter()864 OUString AquaSalInstance::GetDefaultPrinter()
865 {
866     // #i113170# may not be the main thread if called from UNO API
867     SalData::ensureThreadAutoreleasePool();
868 
869     // WinSalInstance::GetDefaultPrinter() fetches current default printer
870     // on every call so do the same here
871     OUString aDefaultPrinter;
872     {
873         NSPrintInfo* pPI = [NSPrintInfo sharedPrintInfo];
874         SAL_WARN_IF( !pPI, "vcl", "no print info" );
875         if( pPI )
876         {
877             NSPrinter* pPr = [pPI printer];
878             SAL_WARN_IF( !pPr, "vcl", "no printer in default info" );
879             if( pPr )
880             {
881                 // Related: tdf#151700 Return the name of the fake printer if
882                 // there are no printers so that the LibreOffice printing code
883                 // will be able to find the fake printer returned by
884                 // AquaSalInstance::GetPrinterQueueInfo()
885                 NSString* pDefName = [pPr name];
886                 SAL_WARN_IF( !pDefName, "vcl", "printer has no name" );
887                 if ( pDefName && [pDefName length])
888                     aDefaultPrinter = GetOUString( pDefName );
889                 else
890                     aDefaultPrinter = getFallbackPrinterName();
891             }
892         }
893     }
894     return aDefaultPrinter;
895 }
896 
CreateInfoPrinter(SalPrinterQueueInfo * pQueueInfo,ImplJobSetup * pSetupData)897 SalInfoPrinter* AquaSalInstance::CreateInfoPrinter( SalPrinterQueueInfo* pQueueInfo,
898                                                     ImplJobSetup* pSetupData )
899 {
900     // #i113170# may not be the main thread if called from UNO API
901     SalData::ensureThreadAutoreleasePool();
902 
903     SalInfoPrinter* pNewInfoPrinter = nullptr;
904     if( pQueueInfo )
905     {
906         pNewInfoPrinter = new AquaSalInfoPrinter( *pQueueInfo );
907         if( pSetupData )
908             pNewInfoPrinter->SetPrinterData( pSetupData );
909     }
910 
911     return pNewInfoPrinter;
912 }
913 
DestroyInfoPrinter(SalInfoPrinter * pPrinter)914 void AquaSalInstance::DestroyInfoPrinter( SalInfoPrinter* pPrinter )
915 {
916     // #i113170# may not be the main thread if called from UNO API
917     SalData::ensureThreadAutoreleasePool();
918 
919     delete pPrinter;
920 }
921 
GetConnectionIdentifier()922 OUString AquaSalInstance::GetConnectionIdentifier()
923 {
924     return OUString();
925 }
926 
927 // We need to re-encode file urls because osl_getFileURLFromSystemPath converts
928 // to UTF-8 before encoding non ascii characters, which is not what other apps expect.
translateToExternalUrl(const OUString & internalUrl)929 static OUString translateToExternalUrl(const OUString& internalUrl)
930 {
931     uno::Reference< uno::XComponentContext > context(
932         comphelper::getProcessComponentContext());
933     return uri::ExternalUriReferenceTranslator::create(context)->translateToExternal(internalUrl);
934 }
935 
936 // #i104525# many versions of OSX have problems with some URLs:
937 // when an app requests OSX to add one of these URLs to the "Recent Items" list
938 // then this app gets killed (TextEdit, Preview, etc. and also OOo)
isDangerousUrl(const OUString & rUrl)939 static bool isDangerousUrl( const OUString& rUrl )
940 {
941     // use a heuristic that detects all known cases since there is no official comment
942     // on the exact impact and root cause of the OSX bug
943     const int nLen = rUrl.getLength();
944     const sal_Unicode* p = rUrl.getStr();
945     for( int i = 0; i < nLen-3; ++i, ++p ) {
946         if( p[0] != '%' )
947             continue;
948         // escaped percent?
949         if( (p[1] == '2') && (p[2] == '5') )
950             return true;
951         // escapes are considered to be UTF-8 encoded
952         // => check for invalid UTF-8 leading byte
953         if( (p[1] != 'f') && (p[1] != 'F') )
954             continue;
955         int cLowNibble = p[2];
956         if( (cLowNibble >= '0' ) && (cLowNibble <= '9'))
957             return false;
958         if( cLowNibble >= 'a' )
959             cLowNibble -= 'a' - 'A';
960         if( (cLowNibble < 'A') || (cLowNibble >= 'C'))
961             return true;
962     }
963 
964     return false;
965 }
966 
AddToRecentDocumentList(const OUString & rFileUrl,const OUString &,const OUString &)967 void AquaSalInstance::AddToRecentDocumentList(const OUString& rFileUrl, const OUString& /*rMimeType*/, const OUString& /*rDocumentService*/)
968 {
969     // Convert file URL for external use (see above)
970     OUString externalUrl = translateToExternalUrl(rFileUrl);
971     if( externalUrl.isEmpty() )
972         externalUrl = rFileUrl;
973 
974     if( !externalUrl.isEmpty() && !isDangerousUrl( externalUrl ) )
975     {
976         NSString* pString = CreateNSString( externalUrl );
977         NSURL* pURL = [NSURL URLWithString: pString];
978 
979         if( pURL )
980         {
981             NSDocumentController* pCtrl = [NSDocumentController sharedDocumentController];
982             [pCtrl noteNewRecentDocumentURL: pURL];
983         }
984         if( pString )
985             [pString release];
986     }
987 }
988 
CreateSalTimer()989 SalTimer* AquaSalInstance::CreateSalTimer()
990 {
991     return new AquaSalTimer();
992 }
993 
CreateSalSystem()994 SalSystem* AquaSalInstance::CreateSalSystem()
995 {
996     return new AquaSalSystem();
997 }
998 
CreateSalBitmap()999 std::shared_ptr<SalBitmap> AquaSalInstance::CreateSalBitmap()
1000 {
1001 #if HAVE_FEATURE_SKIA
1002     if (SkiaHelper::isVCLSkiaEnabled())
1003         return std::make_shared<SkiaSalBitmap>();
1004     else
1005 #endif
1006         return std::make_shared<QuartzSalBitmap>();
1007 }
1008 
getOSVersion()1009 OUString AquaSalInstance::getOSVersion()
1010 {
1011     NSString * versionString = nullptr;
1012     NSDictionary * sysVersionDict = [ NSDictionary dictionaryWithContentsOfFile: @"/System/Library/CoreServices/SystemVersion.plist" ];
1013     if ( sysVersionDict )
1014         versionString = [ sysVersionDict valueForKey: @"ProductVersion" ];
1015 
1016     OUString aVersion = "macOS ";
1017     if ( versionString )
1018         aVersion += OUString::fromUtf8( [ versionString UTF8String ] );
1019     else
1020         aVersion += "(unknown)";
1021 
1022     return aVersion;
1023 }
1024 
CreateCGImage(const Image & rImage)1025 CGImageRef CreateCGImage( const Image& rImage )
1026 {
1027 #if HAVE_FEATURE_SKIA
1028     if (SkiaHelper::isVCLSkiaEnabled())
1029         return SkiaHelper::createCGImage( rImage );
1030 #endif
1031 
1032     BitmapEx aBmpEx( rImage.GetBitmapEx() );
1033     Bitmap aBmp( aBmpEx.GetBitmap() );
1034 
1035     if( aBmp.IsEmpty() || ! aBmp.ImplGetSalBitmap() )
1036         return nullptr;
1037 
1038     // simple case, no transparency
1039     QuartzSalBitmap* pSalBmp = static_cast<QuartzSalBitmap*>(aBmp.ImplGetSalBitmap().get());
1040 
1041     if( ! pSalBmp )
1042         return nullptr;
1043 
1044     CGImageRef xImage = nullptr;
1045     if( !aBmpEx.IsAlpha() )
1046         xImage = pSalBmp->CreateCroppedImage( 0, 0, pSalBmp->mnWidth, pSalBmp->mnHeight );
1047     else
1048     {
1049         AlphaMask aAlphaMask( aBmpEx.GetAlphaMask() );
1050         Bitmap aMask( aAlphaMask.GetBitmap() );
1051         QuartzSalBitmap* pMaskBmp = static_cast<QuartzSalBitmap*>(aMask.ImplGetSalBitmap().get());
1052         if( pMaskBmp )
1053             xImage = pSalBmp->CreateWithMask( *pMaskBmp, 0, 0, pSalBmp->mnWidth, pSalBmp->mnHeight );
1054         else
1055             xImage = pSalBmp->CreateCroppedImage( 0, 0, pSalBmp->mnWidth, pSalBmp->mnHeight );
1056     }
1057 
1058     return xImage;
1059 }
1060 
CreateNSImage(const Image & rImage)1061 NSImage* CreateNSImage( const Image& rImage )
1062 {
1063     CGImageRef xImage = CreateCGImage( rImage );
1064 
1065     if( ! xImage )
1066         return nil;
1067 
1068     Size aSize( rImage.GetSizePixel() );
1069     NSImage* pImage = [[NSImage alloc] initWithSize: NSMakeSize( aSize.Width(), aSize.Height() )];
1070     if( pImage )
1071     {
1072         [pImage lockFocusFlipped:YES];
1073         NSGraphicsContext* pContext = [NSGraphicsContext currentContext];
1074         CGContextRef rCGContext = [pContext CGContext];
1075 
1076         const CGRect aDstRect = { {0, 0}, { static_cast<CGFloat>(aSize.Width()), static_cast<CGFloat>(aSize.Height()) } };
1077         CGContextDrawImage( rCGContext, aDstRect, xImage );
1078 
1079         [pImage unlockFocus];
1080     }
1081 
1082     CGImageRelease( xImage );
1083 
1084     return pImage;
1085 }
1086 
SVMainHook(int * pnInit)1087 bool AquaSalInstance::SVMainHook(int* pnInit)
1088 {
1089     gpnInit = pnInit;
1090 
1091     OUString aExeURL, aExe;
1092     osl_getExecutableFile( &aExeURL.pData );
1093     osl_getSystemPathFromFileURL( aExeURL.pData, &aExe.pData );
1094     OString aByteExe( OUStringToOString( aExe, osl_getThreadTextEncoding() ) );
1095 
1096 #if OSL_DEBUG_LEVEL >= 2
1097     aByteExe += OString ( " NSAccessibilityDebugLogLevel 1" );
1098     const char* pArgv[] = { aByteExe.getStr(), NULL };
1099     NSApplicationMain( 3, pArgv );
1100 #else
1101     const char* pArgv[] = { aByteExe.getStr(), nullptr };
1102     NSApplicationMain( 1, pArgv );
1103 #endif
1104 
1105     return true;
1106 }
1107 
1108 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
1109