Related: tdf#137748 "Update" should have use-underline
[LibreOffice.git] / vcl / osx / salinst.cxx
blob6ac198554fb003384ae978a150cd51f86d5d3549
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
20 #include <sal/config.h>
21 #include <sal/log.hxx>
22 #include <osl/diagnose.h>
24 #include <condition_variable>
25 #include <mutex>
26 #include <utility>
28 #include <config_features.h>
30 #include <stdio.h>
32 #include <comphelper/solarmutex.hxx>
34 #include <comphelper/lok.hxx>
36 #include <osl/process.h>
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>
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>
62 #include <print.h>
64 #include <comphelper/processfactory.hxx>
66 #include <com/sun/star/uri/ExternalUriReferenceTranslator.hpp>
67 #include <com/sun/star/uno/XComponentContext.hpp>
69 #include <premac.h>
70 #include <Foundation/Foundation.h>
71 #include <ApplicationServices/ApplicationServices.h>
72 #import "apple_remote/RemoteMainController.h"
73 #include <apple_remote/RemoteControl.h>
74 #include <postmac.h>
76 #if HAVE_FEATURE_SKIA
77 #include <vcl/skia/SkiaHelper.hxx>
78 #include <skia/salbmp.hxx>
79 #include <skia/osx/gdiimpl.hxx>
80 #include <skia/osx/bitmap.hxx>
81 #endif
83 extern "C" {
84 #include <crt_externs.h>
87 using namespace ::com::sun::star;
89 static int* gpnInit = nullptr;
90 static NSMenu* pDockMenu = nil;
91 static bool bLeftMain = false;
93 namespace {
95 class AquaDelayedSettingsChanged : public Idle
97 bool mbInvalidate;
99 public:
100 AquaDelayedSettingsChanged( bool bInvalidate ) :
101 Idle("AquaSalInstance AquaDelayedSettingsChanged"),
102 mbInvalidate( bInvalidate )
106 virtual void Invoke() override
108 AquaSalInstance *pInst = GetSalData()->mpInstance;
109 SalFrame *pAnyFrame = pInst->anyFrame();
110 if( pAnyFrame )
111 pAnyFrame->CallCallback( SalEvent::SettingsChanged, nullptr );
113 if( mbInvalidate )
115 for( auto pSalFrame : pInst->getFrames() )
117 AquaSalFrame* pFrame = static_cast<AquaSalFrame*>( pSalFrame );
118 if( pFrame->mbShown )
119 pFrame->SendPaintEvent();
122 delete this;
128 void AquaSalInstance::delayedSettingsChanged( bool bInvalidate )
130 osl::Guard< comphelper::SolarMutex > aGuard( *GetYieldMutex() );
131 AquaDelayedSettingsChanged* pIdle = new AquaDelayedSettingsChanged( bInvalidate );
132 pIdle->Start();
135 // the std::list<const ApplicationEvent*> must be available before any SalData/SalInst/etc. objects are ready
136 std::list<const ApplicationEvent*> AquaSalInstance::aAppEventList;
138 NSMenu* AquaSalInstance::GetDynamicDockMenu()
140 if( ! pDockMenu && ! bLeftMain )
141 pDockMenu = [[NSMenu alloc] initWithTitle: @""];
142 return pDockMenu;
145 bool AquaSalInstance::isOnCommandLine( const OUString& rArg )
147 sal_uInt32 nArgs = osl_getCommandArgCount();
148 for( sal_uInt32 i = 0; i < nArgs; i++ )
150 OUString aArg;
151 osl_getCommandArg( i, &aArg.pData );
152 if( aArg.equals( rArg ) )
153 return true;
155 return false;
158 void AquaSalInstance::AfterAppInit()
160 [[NSNotificationCenter defaultCenter] addObserver: NSApp
161 selector: @selector(systemColorsChanged:)
162 name: NSSystemColorsDidChangeNotification
163 object: nil ];
164 [[NSNotificationCenter defaultCenter] addObserver: NSApp
165 selector: @selector(screenParametersChanged:)
166 name: NSApplicationDidChangeScreenParametersNotification
167 object: nil ];
168 // add observers for some settings changes that affect vcl's settings
169 // scrollbar variant
170 [[NSDistributedNotificationCenter defaultCenter] addObserver: NSApp
171 selector: @selector(scrollbarVariantChanged:)
172 name: @"AppleAquaScrollBarVariantChanged"
173 object: nil ];
174 // scrollbar page behavior ("jump to here" or not)
175 [[NSDistributedNotificationCenter defaultCenter] addObserver: NSApp
176 selector: @selector(scrollbarSettingsChanged:)
177 name: @"AppleNoRedisplayAppearancePreferenceChanged"
178 object: nil ];
179 #if !HAVE_FEATURE_MACOSX_SANDBOX
180 // Initialize Apple Remote
181 GetSalData()->mpAppleRemoteMainController = [[AppleRemoteMainController alloc] init];
183 [[NSDistributedNotificationCenter defaultCenter] addObserver: NSApp
184 selector: @selector(applicationWillBecomeActive:)
185 name: @"AppleRemoteWillBecomeActive"
186 object: nil ];
188 [[NSDistributedNotificationCenter defaultCenter] addObserver: NSApp
189 selector: @selector(applicationWillResignActive:)
190 name: @"AppleRemoteWillResignActive"
191 object: nil ];
192 #endif
195 SalYieldMutex::SalYieldMutex()
196 : m_aCodeBlock( nullptr )
200 SalYieldMutex::~SalYieldMutex()
204 void SalYieldMutex::doAcquire( sal_uInt32 nLockCount )
206 AquaSalInstance *pInst = GetSalData()->mpInstance;
207 if ( pInst && pInst->IsMainThread() )
209 if ( pInst->mbNoYieldLock )
210 return;
211 do {
212 RuninmainBlock block = nullptr;
214 std::unique_lock<std::mutex> g(m_runInMainMutex);
215 if (m_aMutex.tryToAcquire()) {
216 assert(m_aCodeBlock == nullptr);
217 m_wakeUpMain = false;
218 break;
220 // wait for doRelease() or RUNINMAIN_* to set the condition
221 m_aInMainCondition.wait(g, [this]() { return m_wakeUpMain; });
222 m_wakeUpMain = false;
223 std::swap(block, m_aCodeBlock);
225 if ( block )
227 assert( !pInst->mbNoYieldLock );
228 pInst->mbNoYieldLock = true;
229 block();
230 pInst->mbNoYieldLock = false;
231 Block_release( block );
232 std::scoped_lock<std::mutex> g(m_runInMainMutex);
233 assert(!m_resultReady);
234 m_resultReady = true;
235 m_aResultCondition.notify_all();
238 while ( true );
240 else
241 m_aMutex.acquire();
242 ++m_nCount;
243 --nLockCount;
245 comphelper::SolarMutex::doAcquire( nLockCount );
248 sal_uInt32 SalYieldMutex::doRelease( const bool bUnlockAll )
250 AquaSalInstance *pInst = GetSalData()->mpInstance;
251 if ( pInst->mbNoYieldLock && pInst->IsMainThread() )
252 return 1;
253 sal_uInt32 nCount;
255 std::scoped_lock<std::mutex> g(m_runInMainMutex);
256 // read m_nCount before doRelease
257 bool const isReleased(bUnlockAll || m_nCount == 1);
258 nCount = comphelper::SolarMutex::doRelease( bUnlockAll );
259 if (isReleased && !pInst->IsMainThread()) {
260 m_wakeUpMain = true;
261 m_aInMainCondition.notify_all();
264 return nCount;
267 bool SalYieldMutex::IsCurrentThread() const
269 if ( !GetSalData()->mpInstance->mbNoYieldLock )
270 return comphelper::SolarMutex::IsCurrentThread();
271 else
272 return GetSalData()->mpInstance->IsMainThread();
275 // some convenience functions regarding the yield mutex, aka solar mutex
277 bool ImplSalYieldMutexTryToAcquire()
279 AquaSalInstance* pInst = GetSalData()->mpInstance;
280 if ( pInst )
281 return pInst->GetYieldMutex()->tryToAcquire();
282 else
283 return false;
286 void ImplSalYieldMutexRelease()
288 AquaSalInstance* pInst = GetSalData()->mpInstance;
289 if ( pInst )
290 pInst->GetYieldMutex()->release();
293 extern "C" {
294 VCLPLUG_OSX_PUBLIC SalInstance* create_SalInstance()
296 SalData* pSalData = new SalData;
298 NSAutoreleasePool * pool = [ [ NSAutoreleasePool alloc ] init ];
299 unlink([[NSString stringWithFormat:@"%@/Library/Saved Application State/%s.savedState/restorecount.plist", NSHomeDirectory(), MACOSX_BUNDLE_IDENTIFIER] UTF8String]);
300 unlink([[NSString stringWithFormat:@"%@/Library/Saved Application State/%s.savedState/restorecount.txt", NSHomeDirectory(), MACOSX_BUNDLE_IDENTIFIER] UTF8String]);
301 [ pool drain ];
303 // create our cocoa NSApplication
304 [VCL_NSApplication sharedApplication];
306 SalData::ensureThreadAutoreleasePool();
308 // put cocoa into multithreaded mode
309 [NSThread detachNewThreadSelector:@selector(enableCocoaThreads:) toTarget:[[CocoaThreadEnabler alloc] init] withObject:nil];
311 // Dark mode is disabled as long as it is not implemented completely. For development purposes, it may be controlled by
312 // environment variables: VCL_MACOS_FORCE_DARK_MODE enable dark mode independent of system settings,
313 // VCL_MACOS_USE_SYSTEM_APPEARANCE to use system settings (light mode or the dark mode as configured within system preferences).
315 // TODO: After implementation of dark mode, this code has to be removed.
317 if (@available(macOS 10.14, iOS 13, *))
319 if (getenv("VCL_MACOS_FORCE_DARK_MODE"))
321 [NSApp setAppearance: [NSAppearance appearanceNamed: NSAppearanceNameDarkAqua]];
323 else
324 if (!getenv("VCL_MACOS_USE_SYSTEM_APPEARANCE"))
325 [NSApp setAppearance: [NSAppearance appearanceNamed: NSAppearanceNameAqua]];
328 // activate our delegate methods
329 [NSApp setDelegate: NSApp];
331 SAL_WARN_IF( pSalData->mpInstance != nullptr, "vcl", "more than one instance created" );
332 AquaSalInstance* pInst = new AquaSalInstance;
334 // init instance (only one instance in this version !!!)
335 pSalData->mpInstance = pInst;
336 // this one is for outside AquaSalInstance::Yield
337 SalData::ensureThreadAutoreleasePool();
338 // no focus rects on NWF
339 ImplGetSVData()->maNWFData.mbNoFocusRects = true;
340 ImplGetSVData()->maNWFData.mbNoActiveTabTextRaise = true;
341 ImplGetSVData()->maNWFData.mbCenteredTabs = true;
342 ImplGetSVData()->maNWFData.mnStatusBarLowerRightOffset = 10;
344 return pInst;
348 AquaSalInstance::AquaSalInstance()
349 : SalInstance(std::make_unique<SalYieldMutex>())
350 , mnActivePrintJobs( 0 )
351 , mbIsLiveResize( false )
352 , mbNoYieldLock( false )
353 , mbTimerProcessed( false )
355 maMainThread = osl::Thread::getCurrentIdentifier();
357 ImplSVData* pSVData = ImplGetSVData();
358 pSVData->maAppData.mxToolkitName = OUString("osx");
359 m_bSupportsOpenGL = true;
361 #if HAVE_FEATURE_SKIA
362 AquaSkiaSalGraphicsImpl::prepareSkia();
363 #endif
366 AquaSalInstance::~AquaSalInstance()
368 [NSApp stop: NSApp];
369 bLeftMain = true;
370 if( pDockMenu )
372 [pDockMenu release];
373 pDockMenu = nil;
376 #if HAVE_FEATURE_SKIA
377 SkiaHelper::cleanup();
378 #endif
381 void AquaSalInstance::TriggerUserEventProcessing()
383 dispatch_async(dispatch_get_main_queue(),^{
384 ImplNSAppPostEvent( AquaSalInstance::YieldWakeupEvent, NO );
388 void AquaSalInstance::ProcessEvent( SalUserEvent aEvent )
390 aEvent.m_pFrame->CallCallback( aEvent.m_nEvent, aEvent.m_pData );
391 maWaitingYieldCond.set();
394 bool AquaSalInstance::IsMainThread() const
396 return osl::Thread::getCurrentIdentifier() == maMainThread;
399 void AquaSalInstance::handleAppDefinedEvent( NSEvent* pEvent )
401 AquaSalTimer *pTimer = static_cast<AquaSalTimer*>( ImplGetSVData()->maSchedCtx.mpSalTimer );
402 int nSubtype = [pEvent subtype];
403 switch( nSubtype )
405 case AppStartTimerEvent:
406 if ( pTimer )
407 pTimer->handleStartTimerEvent( pEvent );
408 break;
409 case AppExecuteSVMain:
411 int nRet = ImplSVMain();
412 if (gpnInit)
413 *gpnInit = nRet;
414 [NSApp stop: NSApp];
415 break;
417 case DispatchTimerEvent:
419 AquaSalInstance *pInst = GetSalData()->mpInstance;
420 if ( pTimer && pInst )
421 pInst->mbTimerProcessed = pTimer->handleDispatchTimerEvent( pEvent );
422 break;
424 #if !HAVE_FEATURE_MACOSX_SANDBOX
425 case AppleRemoteControlEvent: // Defined in <apple_remote/RemoteMainController.h>
427 MediaCommand nCommand;
428 AquaSalInstance *pInst = GetSalData()->mpInstance;
429 bool bIsFullScreenMode = false;
431 for( auto pSalFrame : pInst->getFrames() )
433 const AquaSalFrame* pFrame = static_cast<const AquaSalFrame*>( pSalFrame );
434 if ( pFrame->mbFullScreen )
436 bIsFullScreenMode = true;
437 break;
441 switch ([pEvent data1])
443 case kRemoteButtonPlay:
444 nCommand = bIsFullScreenMode ? MediaCommand::PlayPause : MediaCommand::Play;
445 break;
447 // kept for experimentation purpose (scheduled for future implementation)
448 // case kRemoteButtonMenu: nCommand = MediaCommand::Menu; break;
450 case kRemoteButtonPlus: nCommand = MediaCommand::VolumeUp; break;
452 case kRemoteButtonMinus: nCommand = MediaCommand::VolumeDown; break;
454 case kRemoteButtonRight: nCommand = MediaCommand::NextTrack; break;
456 case kRemoteButtonRight_Hold: nCommand = MediaCommand::NextTrackHold; break;
458 case kRemoteButtonLeft: nCommand = MediaCommand::PreviousTrack; break;
460 case kRemoteButtonLeft_Hold: nCommand = MediaCommand::Rewind; break;
462 case kRemoteButtonPlay_Hold: nCommand = MediaCommand::PlayHold; break;
464 case kRemoteButtonMenu_Hold: nCommand = MediaCommand::Stop; break;
466 // FIXME : not detected
467 case kRemoteButtonPlus_Hold:
468 case kRemoteButtonMinus_Hold:
469 break;
471 default:
472 break;
474 AquaSalFrame* pFrame = static_cast<AquaSalFrame*>( pInst->anyFrame() );
475 vcl::Window* pWindow = pFrame ? pFrame->GetWindow() : nullptr;
476 if( pWindow )
478 const Point aPoint;
479 CommandMediaData aMediaData(nCommand);
480 CommandEvent aCEvt( aPoint, CommandEventId::Media, false, &aMediaData );
481 NotifyEvent aNCmdEvt( MouseNotifyEvent::COMMAND, pWindow, &aCEvt );
483 if ( !ImplCallPreNotify( aNCmdEvt ) )
484 pWindow->Command( aCEvt );
488 break;
489 #endif
491 case YieldWakeupEvent:
492 // do nothing, fall out of Yield
493 break;
495 default:
496 OSL_FAIL( "unhandled NSEventTypeApplicationDefined event" );
497 break;
501 bool AquaSalInstance::RunInMainYield( bool bHandleAllCurrentEvents )
503 OSX_SALDATA_RUNINMAIN_UNION( DoYield( false, bHandleAllCurrentEvents), boolean )
504 assert( false && "Don't call this from the main thread!" );
505 return false;
509 static bool isWakeupEvent( NSEvent *pEvent )
511 return NSEventTypeApplicationDefined == [pEvent type]
512 && AquaSalInstance::YieldWakeupEvent == static_cast<int>([pEvent subtype]);
515 bool AquaSalInstance::DoYield(bool bWait, bool bHandleAllCurrentEvents)
517 // ensure that the per thread autorelease pool is top level and
518 // will therefore not be destroyed by cocoa implicitly
519 SalData::ensureThreadAutoreleasePool();
521 // NSAutoreleasePool documentation suggests we should have
522 // an own pool for each yield level
523 ReleasePoolHolder aReleasePool;
525 // first, process current user events
526 bool bHadEvent = DispatchUserEvents( bHandleAllCurrentEvents );
527 if ( !bHandleAllCurrentEvents && bHadEvent )
528 return true;
530 // handle cocoa event queue
531 // cocoa events may be only handled in the thread the NSApp was created
532 if( IsMainThread() && mnActivePrintJobs == 0 )
534 // handle available events
535 NSEvent* pEvent = nil;
536 NSTimeInterval now = [[NSProcessInfo processInfo] systemUptime];
537 mbTimerProcessed = false;
541 SolarMutexReleaser aReleaser;
543 pEvent = [NSApp nextEventMatchingMask: NSEventMaskAny
544 untilDate: nil
545 inMode: NSDefaultRunLoopMode
546 dequeue: YES];
547 if( pEvent )
549 [NSApp sendEvent: pEvent];
550 if ( isWakeupEvent( pEvent ) )
551 continue;
552 bHadEvent = true;
555 [NSApp updateWindows];
557 if ( !bHandleAllCurrentEvents || !pEvent || now < [pEvent timestamp] )
558 break;
560 while( true );
562 AquaSalTimer *pTimer = static_cast<AquaSalTimer*>( ImplGetSVData()->maSchedCtx.mpSalTimer );
563 if ( !mbTimerProcessed && pTimer && pTimer->IsDirectTimeout() )
565 pTimer->handleTimerElapsed();
566 bHadEvent = true;
569 // if we had no event yet, wait for one if requested
570 if( bWait && ! bHadEvent )
572 SolarMutexReleaser aReleaser;
574 pEvent = [NSApp nextEventMatchingMask: NSEventMaskAny
575 untilDate: [NSDate distantFuture]
576 inMode: NSDefaultRunLoopMode
577 dequeue: YES];
578 if( pEvent )
580 [NSApp sendEvent: pEvent];
581 if ( !isWakeupEvent( pEvent ) )
582 bHadEvent = true;
584 [NSApp updateWindows];
587 // collect update rectangles
588 for( auto pSalFrame : GetSalData()->mpInstance->getFrames() )
590 AquaSalFrame* pFrame = static_cast<AquaSalFrame*>( pSalFrame );
591 if( pFrame->mbShown && ! pFrame->maInvalidRect.IsEmpty() )
593 pFrame->Flush( pFrame->maInvalidRect );
594 pFrame->maInvalidRect.SetEmpty();
598 if ( bHadEvent )
599 maWaitingYieldCond.set();
601 else
603 bHadEvent = RunInMainYield( bHandleAllCurrentEvents );
604 if ( !bHadEvent && bWait )
606 // #i103162#
607 // wait until the main thread has dispatched an event
608 maWaitingYieldCond.reset();
609 SolarMutexReleaser aReleaser;
610 maWaitingYieldCond.wait();
614 // we get some apple events way too early
615 // before the application is ready to handle them,
616 // so their corresponding application events need to be delayed
617 // now is a good time to handle at least one of them
618 if( bWait && !aAppEventList.empty() && ImplGetSVData()->maAppData.mbInAppExecute )
620 // make sure that only one application event is active at a time
621 static bool bInAppEvent = false;
622 if( !bInAppEvent )
624 bInAppEvent = true;
625 // get the next delayed application event
626 const ApplicationEvent* pAppEvent = aAppEventList.front();
627 aAppEventList.pop_front();
628 // handle one application event (no recursion)
629 const ImplSVData* pSVData = ImplGetSVData();
630 pSVData->mpApp->AppEvent( *pAppEvent );
631 delete pAppEvent;
632 // allow the next delayed application event
633 bInAppEvent = false;
637 return bHadEvent;
640 bool AquaSalInstance::AnyInput( VclInputFlags nType )
642 if( nType & VclInputFlags::APPEVENT )
644 if( ! aAppEventList.empty() )
645 return true;
646 if( nType == VclInputFlags::APPEVENT )
647 return false;
650 OSX_INST_RUNINMAIN_UNION( AnyInput( nType ), boolean )
652 if( nType & VclInputFlags::TIMER )
654 AquaSalTimer *pTimer = static_cast<AquaSalTimer*>( ImplGetSVData()->maSchedCtx.mpSalTimer );
655 if (pTimer && pTimer->IsTimerElapsed())
656 return true;
659 unsigned/*NSUInteger*/ nEventMask = 0;
660 if( nType & VclInputFlags::MOUSE)
661 nEventMask |=
662 NSEventMaskLeftMouseDown | NSEventMaskRightMouseDown | NSEventMaskOtherMouseDown |
663 NSEventMaskLeftMouseUp | NSEventMaskRightMouseUp | NSEventMaskOtherMouseUp |
664 NSEventMaskLeftMouseDragged | NSEventMaskRightMouseDragged | NSEventMaskOtherMouseDragged |
665 NSEventMaskScrollWheel |
666 // NSEventMaskMouseMoved |
667 NSEventMaskMouseEntered | NSEventMaskMouseExited;
668 if( nType & VclInputFlags::KEYBOARD)
669 nEventMask |= NSEventMaskKeyDown | NSEventMaskKeyUp | NSEventMaskFlagsChanged;
670 if( nType & VclInputFlags::OTHER)
671 nEventMask |= NSEventMaskTabletPoint | NSEventMaskApplicationDefined;
672 // TODO: VclInputFlags::PAINT / more VclInputFlags::OTHER
673 if( !bool(nType) )
674 return false;
676 NSEvent* pEvent = [NSApp nextEventMatchingMask: nEventMask untilDate: nil
677 inMode: NSDefaultRunLoopMode dequeue: NO];
678 return (pEvent != nullptr);
681 SalFrame* AquaSalInstance::CreateChildFrame( SystemParentData*, SalFrameStyleFlags /*nSalFrameStyle*/ )
683 return nullptr;
686 SalFrame* AquaSalInstance::CreateFrame( SalFrame* pParent, SalFrameStyleFlags nSalFrameStyle )
688 OSX_INST_RUNINMAIN_POINTER( CreateFrame( pParent, nSalFrameStyle ), SalFrame* )
689 return new AquaSalFrame( pParent, nSalFrameStyle );
692 void AquaSalInstance::DestroyFrame( SalFrame* pFrame )
694 OSX_INST_RUNINMAIN( DestroyFrame( pFrame ) )
695 delete pFrame;
698 SalObject* AquaSalInstance::CreateObject( SalFrame* pParent, SystemWindowData* pWindowData, bool /* bShow */ )
700 if ( !pParent )
701 return nullptr;
703 OSX_INST_RUNINMAIN_POINTER( CreateObject( pParent, pWindowData, false ), SalObject* )
704 return new AquaSalObject( static_cast<AquaSalFrame*>(pParent), pWindowData );
707 void AquaSalInstance::DestroyObject( SalObject* pObject )
709 OSX_INST_RUNINMAIN( DestroyObject( pObject ) )
710 delete pObject;
713 std::unique_ptr<SalPrinter> AquaSalInstance::CreatePrinter( SalInfoPrinter* pInfoPrinter )
715 return std::unique_ptr<SalPrinter>(new AquaSalPrinter( dynamic_cast<AquaSalInfoPrinter*>(pInfoPrinter) ));
718 void AquaSalInstance::GetPrinterQueueInfo( ImplPrnQueueList* pList )
720 NSArray* pNames = [NSPrinter printerNames];
721 NSArray* pTypes = [NSPrinter printerTypes];
722 unsigned int nNameCount = pNames ? [pNames count] : 0;
723 unsigned int nTypeCount = pTypes ? [pTypes count] : 0;
724 SAL_WARN_IF( nTypeCount != nNameCount, "vcl", "type count not equal to printer count" );
725 for( unsigned int i = 0; i < nNameCount; i++ )
727 NSString* pName = [pNames objectAtIndex: i];
728 NSString* pType = i < nTypeCount ? [pTypes objectAtIndex: i] : nil;
729 if( pName )
731 std::unique_ptr<SalPrinterQueueInfo> pInfo(new SalPrinterQueueInfo);
732 pInfo->maPrinterName = GetOUString( pName );
733 if( pType )
734 pInfo->maDriver = GetOUString( pType );
735 pInfo->mnStatus = PrintQueueFlags::NONE;
736 pInfo->mnJobs = 0;
738 pList->Add( std::move(pInfo) );
743 void AquaSalInstance::GetPrinterQueueState( SalPrinterQueueInfo* )
747 OUString AquaSalInstance::GetDefaultPrinter()
749 // #i113170# may not be the main thread if called from UNO API
750 SalData::ensureThreadAutoreleasePool();
752 if( maDefaultPrinter.isEmpty() )
754 NSPrintInfo* pPI = [NSPrintInfo sharedPrintInfo];
755 SAL_WARN_IF( !pPI, "vcl", "no print info" );
756 if( pPI )
758 NSPrinter* pPr = [pPI printer];
759 SAL_WARN_IF( !pPr, "vcl", "no printer in default info" );
760 if( pPr )
762 NSString* pDefName = [pPr name];
763 SAL_WARN_IF( !pDefName, "vcl", "printer has no name" );
764 maDefaultPrinter = GetOUString( pDefName );
768 return maDefaultPrinter;
771 SalInfoPrinter* AquaSalInstance::CreateInfoPrinter( SalPrinterQueueInfo* pQueueInfo,
772 ImplJobSetup* pSetupData )
774 // #i113170# may not be the main thread if called from UNO API
775 SalData::ensureThreadAutoreleasePool();
777 SalInfoPrinter* pNewInfoPrinter = nullptr;
778 if( pQueueInfo )
780 pNewInfoPrinter = new AquaSalInfoPrinter( *pQueueInfo );
781 if( pSetupData )
782 pNewInfoPrinter->SetPrinterData( pSetupData );
785 return pNewInfoPrinter;
788 void AquaSalInstance::DestroyInfoPrinter( SalInfoPrinter* pPrinter )
790 // #i113170# may not be the main thread if called from UNO API
791 SalData::ensureThreadAutoreleasePool();
793 delete pPrinter;
796 OUString AquaSalInstance::GetConnectionIdentifier()
798 return OUString();
801 // We need to re-encode file urls because osl_getFileURLFromSystemPath converts
802 // to UTF-8 before encoding non ascii characters, which is not what other apps expect.
803 static OUString translateToExternalUrl(const OUString& internalUrl)
805 uno::Reference< uno::XComponentContext > context(
806 comphelper::getProcessComponentContext());
807 return uri::ExternalUriReferenceTranslator::create(context)->translateToExternal(internalUrl);
810 // #i104525# many versions of OSX have problems with some URLs:
811 // when an app requests OSX to add one of these URLs to the "Recent Items" list
812 // then this app gets killed (TextEdit, Preview, etc. and also OOo)
813 static bool isDangerousUrl( const OUString& rUrl )
815 // use a heuristic that detects all known cases since there is no official comment
816 // on the exact impact and root cause of the OSX bug
817 const int nLen = rUrl.getLength();
818 const sal_Unicode* p = rUrl.getStr();
819 for( int i = 0; i < nLen-3; ++i, ++p ) {
820 if( p[0] != '%' )
821 continue;
822 // escaped percent?
823 if( (p[1] == '2') && (p[2] == '5') )
824 return true;
825 // escapes are considered to be UTF-8 encoded
826 // => check for invalid UTF-8 leading byte
827 if( (p[1] != 'f') && (p[1] != 'F') )
828 continue;
829 int cLowNibble = p[2];
830 if( (cLowNibble >= '0' ) && (cLowNibble <= '9'))
831 return false;
832 if( cLowNibble >= 'a' )
833 cLowNibble -= 'a' - 'A';
834 if( (cLowNibble < 'A') || (cLowNibble >= 'C'))
835 return true;
838 return false;
841 void AquaSalInstance::AddToRecentDocumentList(const OUString& rFileUrl, const OUString& /*rMimeType*/, const OUString& /*rDocumentService*/)
843 // Convert file URL for external use (see above)
844 OUString externalUrl = translateToExternalUrl(rFileUrl);
845 if( externalUrl.isEmpty() )
846 externalUrl = rFileUrl;
848 if( !externalUrl.isEmpty() && !isDangerousUrl( externalUrl ) )
850 NSString* pString = CreateNSString( externalUrl );
851 NSURL* pURL = [NSURL URLWithString: pString];
853 if( pURL )
855 NSDocumentController* pCtrl = [NSDocumentController sharedDocumentController];
856 [pCtrl noteNewRecentDocumentURL: pURL];
858 if( pString )
859 [pString release];
863 SalTimer* AquaSalInstance::CreateSalTimer()
865 return new AquaSalTimer();
868 SalSystem* AquaSalInstance::CreateSalSystem()
870 return new AquaSalSystem();
873 std::shared_ptr<SalBitmap> AquaSalInstance::CreateSalBitmap()
875 #if HAVE_FEATURE_SKIA
876 if (SkiaHelper::isVCLSkiaEnabled())
877 return std::make_shared<SkiaSalBitmap>();
878 else
879 #endif
880 return std::make_shared<QuartzSalBitmap>();
883 OUString AquaSalInstance::getOSVersion()
885 NSString * versionString = nullptr;
886 NSDictionary * sysVersionDict = [ NSDictionary dictionaryWithContentsOfFile: @"/System/Library/CoreServices/SystemVersion.plist" ];
887 if ( sysVersionDict )
888 versionString = [ sysVersionDict valueForKey: @"ProductVersion" ];
890 OUString aVersion = "Mac OS X ";
891 if ( versionString )
892 aVersion += OUString::fromUtf8( [ versionString UTF8String ] );
893 else
894 aVersion += "(unknown)";
896 return aVersion;
899 CGImageRef CreateCGImage( const Image& rImage )
901 #if HAVE_FEATURE_SKIA
902 if (SkiaHelper::isVCLSkiaEnabled())
903 return SkiaHelper::createCGImage( rImage );
904 #endif
906 BitmapEx aBmpEx( rImage.GetBitmapEx() );
907 Bitmap aBmp( aBmpEx.GetBitmap() );
909 if( aBmp.IsEmpty() || ! aBmp.ImplGetSalBitmap() )
910 return nullptr;
912 // simple case, no transparency
913 QuartzSalBitmap* pSalBmp = static_cast<QuartzSalBitmap*>(aBmp.ImplGetSalBitmap().get());
915 if( ! pSalBmp )
916 return nullptr;
918 CGImageRef xImage = nullptr;
919 if( !aBmpEx.IsAlpha() )
920 xImage = pSalBmp->CreateCroppedImage( 0, 0, pSalBmp->mnWidth, pSalBmp->mnHeight );
921 else
923 AlphaMask aAlphaMask( aBmpEx.GetAlpha() );
924 Bitmap aMask( aAlphaMask.GetBitmap() );
925 QuartzSalBitmap* pMaskBmp = static_cast<QuartzSalBitmap*>(aMask.ImplGetSalBitmap().get());
926 if( pMaskBmp )
927 xImage = pSalBmp->CreateWithMask( *pMaskBmp, 0, 0, pSalBmp->mnWidth, pSalBmp->mnHeight );
928 else
929 xImage = pSalBmp->CreateCroppedImage( 0, 0, pSalBmp->mnWidth, pSalBmp->mnHeight );
932 return xImage;
935 NSImage* CreateNSImage( const Image& rImage )
937 CGImageRef xImage = CreateCGImage( rImage );
939 if( ! xImage )
940 return nil;
942 Size aSize( rImage.GetSizePixel() );
943 NSImage* pImage = [[NSImage alloc] initWithSize: NSMakeSize( aSize.Width(), aSize.Height() )];
944 if( pImage )
946 [pImage lockFocusFlipped:YES];
947 NSGraphicsContext* pContext = [NSGraphicsContext currentContext];
948 CGContextRef rCGContext = [pContext CGContext];
950 const CGRect aDstRect = { {0, 0}, { static_cast<CGFloat>(aSize.Width()), static_cast<CGFloat>(aSize.Height()) } };
951 CGContextDrawImage( rCGContext, aDstRect, xImage );
953 [pImage unlockFocus];
956 CGImageRelease( xImage );
958 return pImage;
961 bool AquaSalInstance::SVMainHook(int* pnInit)
963 gpnInit = pnInit;
965 OUString aExeURL, aExe;
966 osl_getExecutableFile( &aExeURL.pData );
967 osl_getSystemPathFromFileURL( aExeURL.pData, &aExe.pData );
968 OString aByteExe( OUStringToOString( aExe, osl_getThreadTextEncoding() ) );
970 #ifdef DEBUG
971 aByteExe += OString ( " NSAccessibilityDebugLogLevel 1" );
972 const char* pArgv[] = { aByteExe.getStr(), NULL };
973 NSApplicationMain( 3, pArgv );
974 #else
975 const char* pArgv[] = { aByteExe.getStr(), nullptr };
976 NSApplicationMain( 1, pArgv );
977 #endif
979 return true;
982 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */