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