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