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