1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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>
27 #include <sal/types.h>
29 #include <vcl/virdev.hxx>
30 #include <vcl/inputtypes.hxx>
31 #include <vcl/lok.hxx>
33 #include <headless/svpinst.hxx>
34 #include <headless/svpframe.hxx>
35 #include <headless/svpdummies.hxx>
36 #include <headless/svpvd.hxx>
38 # include <quartz/salbmp.h>
39 # include <quartz/salgdi.h>
40 # include <quartz/salvd.h>
43 # include <headless/svpgdi.hxx>
45 #include <headless/svpbmp.hxx>
47 #include <salframe.hxx>
49 // FIXME: remove when we re-work the svp mainloop
50 #include <unx/salunxtime.h>
51 #include <tools/debug.hxx>
52 #include <comphelper/lok.hxx>
53 #include <o3tl/unreachable.hxx>
55 #if defined EMSCRIPTEN
56 #include <emscripten.h>
59 SvpSalInstance
* SvpSalInstance::s_pDefaultInstance
= nullptr;
62 static bool g_CheckedMutex
= false;
64 #define DBG_TESTSVPYIELDMUTEX() \
66 if (!g_CheckedMutex) \
68 assert(dynamic_cast<SvpSalYieldMutex*>(GetYieldMutex()) != nullptr \
69 && "This SvpSalInstance function requires use of SvpSalYieldMutex"); \
70 g_CheckedMutex = true; \
75 #define DBG_TESTSVPYIELDMUTEX() ((void)0)
78 #if !defined(ANDROID) && !defined(IOS) && !defined(EMSCRIPTEN)
80 static void atfork_child()
82 if (SvpSalInstance::s_pDefaultInstance
!= nullptr)
84 SvpSalInstance::s_pDefaultInstance
->CloseWakeupPipe();
90 SvpSalInstance::SvpSalInstance( std::unique_ptr
<SalYieldMutex
> pMutex
)
91 : SalGenericInstance( std::move(pMutex
) )
93 m_aTimeout
.tv_sec
= 0;
94 m_aTimeout
.tv_usec
= 0;
97 m_MainThread
= osl::Thread::getCurrentIdentifier();
98 if( s_pDefaultInstance
== nullptr )
99 s_pDefaultInstance
= this;
100 #if !defined(ANDROID) && !defined(IOS) && !defined(EMSCRIPTEN)
101 pthread_atfork(nullptr, nullptr, atfork_child
);
103 #if defined EMSCRIPTEN
104 ImplGetSVData()->maAppData
.m_bUseSystemLoop
= true;
108 SvpSalInstance::~SvpSalInstance()
110 if( s_pDefaultInstance
== this )
111 s_pDefaultInstance
= nullptr;
115 void SvpSalInstance::CloseWakeupPipe()
117 SvpSalYieldMutex
*const pMutex(dynamic_cast<SvpSalYieldMutex
*>(GetYieldMutex()));
120 while (!pMutex
->m_FeedbackPipe
.empty())
121 pMutex
->m_FeedbackPipe
.pop();
124 void SvpSalInstance::TriggerUserEventProcessing()
129 void SvpSalInstance::Wakeup(SvpRequest
const request
)
131 DBG_TESTSVPYIELDMUTEX();
133 ImplSVData
* pSVData
= ImplGetSVData();
134 if (pSVData
->mpWakeCallback
&& pSVData
->mpPollClosure
)
135 pSVData
->mpWakeCallback(pSVData
->mpPollClosure
);
137 SvpSalYieldMutex
*const pMutex(static_cast<SvpSalYieldMutex
*>(GetYieldMutex()));
138 std::scoped_lock
<std::mutex
> g(pMutex
->m_WakeUpMainMutex
);
139 if (request
!= SvpRequest::NONE
)
140 pMutex
->m_Request
= request
;
141 pMutex
->m_wakeUpMain
= true;
142 pMutex
->m_WakeUpMainCond
.notify_one();
145 bool SvpSalInstance::CheckTimeout( bool bExecuteTimers
)
148 if( m_aTimeout
.tv_sec
) // timer is started
151 gettimeofday( &aTimeOfDay
, nullptr );
152 if( aTimeOfDay
>= m_aTimeout
)
157 // timed out, update timeout
158 m_aTimeout
= aTimeOfDay
;
159 m_aTimeout
+= m_nTimeoutMS
;
161 osl::Guard
< comphelper::SolarMutex
> aGuard( GetYieldMutex() );
164 ImplSVData
* pSVData
= ImplGetSVData();
165 if( pSVData
->maSchedCtx
.mpSalTimer
)
166 pSVData
->maSchedCtx
.mpSalTimer
->CallCallback();
173 SalFrame
* SvpSalInstance::CreateChildFrame( SystemParentData
* /*pParent*/, SalFrameStyleFlags nStyle
)
175 return new SvpSalFrame( this, nullptr, nStyle
);
178 SalFrame
* SvpSalInstance::CreateFrame( SalFrame
* pParent
, SalFrameStyleFlags nStyle
)
180 return new SvpSalFrame( this, pParent
, nStyle
);
183 void SvpSalInstance::DestroyFrame( SalFrame
* pFrame
)
188 SalObject
* SvpSalInstance::CreateObject( SalFrame
*, SystemWindowData
*, bool )
190 return new SvpSalObject
;
193 void SvpSalInstance::DestroyObject( SalObject
* pObject
)
200 std::unique_ptr
<SalVirtualDevice
> SvpSalInstance::CreateVirtualDevice(SalGraphics
& rGraphics
,
201 tools::Long
&nDX
, tools::Long
&nDY
,
202 DeviceFormat
/*eFormat*/,
203 const SystemGraphicsData
* pGd
)
205 SvpSalGraphics
*pSvpSalGraphics
= dynamic_cast<SvpSalGraphics
*>(&rGraphics
);
206 assert(pSvpSalGraphics
);
208 // tdf#127529 normally pPreExistingTarget is null and we are a true virtualdevice drawing to a backing buffer.
209 // Occasionally, for canvas/slideshow, pPreExistingTarget is pre-provided as a hack to use the vcl drawing
210 // apis to render onto a preexisting cairo surface. The necessity for that precedes the use of cairo in vcl proper
211 cairo_surface_t
* pPreExistingTarget
= pGd
? static_cast<cairo_surface_t
*>(pGd
->pSurface
) : nullptr;
215 cairo_surface_t
* pPreExistingTarget
= nullptr;
217 std::unique_ptr
<SalVirtualDevice
> xNew(new SvpSalVirtualDevice(pSvpSalGraphics
->getSurface(), pPreExistingTarget
));
218 if (!xNew
->SetSize(nDX
, nDY
))
223 cairo_surface_t
* get_underlying_cairo_surface(const VirtualDevice
& rDevice
)
225 return static_cast<SvpSalVirtualDevice
*>(rDevice
.mpVirDev
.get())->GetSurface();
228 const cairo_font_options_t
* SvpSalInstance::GetCairoFontOptions()
230 static cairo_font_options_t
*gOptions
= nullptr;
233 gOptions
= cairo_font_options_create();
234 cairo_font_options_set_antialias(gOptions
, CAIRO_ANTIALIAS_GRAY
);
241 const cairo_font_options_t
* SvpSalInstance::GetCairoFontOptions()
248 SalTimer
* SvpSalInstance::CreateSalTimer()
250 return new SvpSalTimer( this );
253 SalSystem
* SvpSalInstance::CreateSalSystem()
255 return new SvpSalSystem();
258 std::shared_ptr
<SalBitmap
> SvpSalInstance::CreateSalBitmap()
261 return std::make_shared
<QuartzSalBitmap
>();
263 return std::make_shared
<SvpSalBitmap
>();
267 void SvpSalInstance::ProcessEvent( SalUserEvent aEvent
)
269 DBG_TESTSVPYIELDMUTEX();
271 aEvent
.m_pFrame
->CallCallback( aEvent
.m_nEvent
, aEvent
.m_pData
);
272 if( aEvent
.m_nEvent
== SalEvent::Resize
)
274 // this would be a good time to post a paint
275 const SvpSalFrame
* pSvpFrame
= static_cast<const SvpSalFrame
*>( aEvent
.m_pFrame
);
276 pSvpFrame
->PostPaint();
279 SvpSalYieldMutex
*const pMutex(static_cast<SvpSalYieldMutex
*>(GetYieldMutex()));
280 pMutex
->m_NonMainWaitingYieldCond
.set();
283 #if defined EMSCRIPTEN
285 static void loop(void * arg
) {
287 static_cast<SvpSalInstance
*>(arg
)->ImplYield(false, false);
290 bool SvpSalInstance::DoExecute(int &) {
291 assert(Application::IsUseSystemEventLoop());
292 // emscripten_set_main_loop will unwind the stack by throwing a JavaScript exception, so we need
293 // to manually undo the call of AcquireYieldMutex() done in InitVCL:
294 ReleaseYieldMutex(false);
295 // Somewhat randomly use an fps=100 argument so the loop callback is called 100 times per
297 emscripten_set_main_loop_arg(loop
, this, 100, 1);
301 void SvpSalInstance::DoQuit() {
302 assert(Application::IsUseSystemEventLoop());
307 SvpSalYieldMutex::SvpSalYieldMutex()
311 SvpSalYieldMutex::~SvpSalYieldMutex()
315 void SvpSalYieldMutex::doAcquire(sal_uInt32
const nLockCount
)
317 auto *const pInst
= static_cast<SvpSalInstance
*>(GetSalInstance());
318 if (pInst
&& pInst
->IsMainThread())
325 SvpRequest request
= SvpRequest::NONE
;
327 std::unique_lock
<std::mutex
> g(m_WakeUpMainMutex
);
328 if (m_aMutex
.tryToAcquire()) {
329 // if there's a request, the other thread holds m_aMutex
330 assert(m_Request
== SvpRequest::NONE
);
331 m_wakeUpMain
= false;
334 m_WakeUpMainCond
.wait(g
, [this]() { return m_wakeUpMain
; });
335 m_wakeUpMain
= false;
336 std::swap(m_Request
, request
);
338 if (request
!= SvpRequest::NONE
)
340 // nested Yield on behalf of another thread
341 assert(!m_bNoYieldLock
);
342 m_bNoYieldLock
= true;
343 bool const bEvents
= pInst
->DoYield(false, request
== SvpRequest::MainThreadDispatchAllEvents
);
344 m_bNoYieldLock
= false;
346 std::lock_guard
lock(m_FeedbackMutex
);
347 m_FeedbackPipe
.push(bEvents
);
349 m_FeedbackCV
.notify_all();
359 SalYieldMutex::doAcquire(nLockCount
- 1);
362 sal_uInt32
SvpSalYieldMutex::doRelease(bool const bUnlockAll
)
364 auto *const pInst
= static_cast<SvpSalInstance
*>(GetSalInstance());
365 if (pInst
&& pInst
->IsMainThread())
370 return SalYieldMutex::doRelease(bUnlockAll
);
374 // read m_nCount before doRelease
375 bool const isReleased(bUnlockAll
|| m_nCount
== 1);
376 nCount
= comphelper::SolarMutex::doRelease( bUnlockAll
);
380 if (vcl::lok::isUnipoll())
387 std::scoped_lock
<std::mutex
> g(m_WakeUpMainMutex
);
389 m_WakeUpMainCond
.notify_one();
396 bool SvpSalYieldMutex::IsCurrentThread() const
398 if (GetSalInstance()->IsMainThread() && m_bNoYieldLock
)
401 return SalYieldMutex::IsCurrentThread();
404 bool SvpSalInstance::IsMainThread() const
406 return osl::Thread::getCurrentIdentifier() == m_MainThread
;
409 void SvpSalInstance::updateMainThread()
413 m_MainThread
= osl::Thread::getCurrentIdentifier();
414 ImplGetSVData()->mnMainThreadId
= osl::Thread::getCurrentIdentifier();
418 bool SvpSalInstance::ImplYield(bool bWait
, bool bHandleAllCurrentEvents
)
420 DBG_TESTSVPYIELDMUTEX();
421 DBG_TESTSOLARMUTEX();
422 assert(IsMainThread());
424 bool bWasEvent
= DispatchUserEvents(bHandleAllCurrentEvents
);
425 if (!bHandleAllCurrentEvents
&& bWasEvent
)
428 // CheckTimeout() invokes the sal timer, which invokes the scheduler.
429 bWasEvent
= CheckTimeout() || bWasEvent
;
430 const bool bMustSleep
= bWait
&& !bWasEvent
;
432 // This is wrong and must be removed!
433 // We always want to drop the SolarMutex on yield; that is the whole point of yield.
434 // If we know the LOK client has pending input events, then don't yet return, so those events
435 // can be processed as well.
436 if (!bMustSleep
&& !comphelper::LibreOfficeKit::anyInput())
439 sal_Int64 nTimeoutMicroS
= 0;
442 if (m_aTimeout
.tv_sec
) // Timer is started.
445 // determine remaining timeout.
446 gettimeofday (&Timeout
, nullptr);
447 if (m_aTimeout
> Timeout
)
448 nTimeoutMicroS
= ((m_aTimeout
.tv_sec
- Timeout
.tv_sec
) * 1000 * 1000 +
449 (m_aTimeout
.tv_usec
- Timeout
.tv_usec
));
452 nTimeoutMicroS
= -1; // wait until something happens
455 SolarMutexReleaser aReleaser
;
457 if (vcl::lok::isUnipoll())
459 ImplSVData
* pSVData
= ImplGetSVData();
460 if (pSVData
->mpPollClosure
)
462 int nPollResult
= pSVData
->mpPollCallback(pSVData
->mpPollClosure
, nTimeoutMicroS
);
464 pSVData
->maAppData
.mbAppQuit
= true;
465 bWasEvent
= bWasEvent
|| (nPollResult
!= 0);
470 SvpSalYieldMutex
*const pMutex(static_cast<SvpSalYieldMutex
*>(GetYieldMutex()));
471 std::unique_lock
<std::mutex
> g(pMutex
->m_WakeUpMainMutex
);
472 // wait for doRelease() or Wakeup() to set the condition
473 if (nTimeoutMicroS
== -1)
475 pMutex
->m_WakeUpMainCond
.wait(g
,
476 [pMutex
]() { return pMutex
->m_wakeUpMain
; });
480 int nTimeoutMS
= nTimeoutMicroS
/ 1000;
481 if (nTimeoutMicroS
% 1000)
483 pMutex
->m_WakeUpMainCond
.wait_for(g
,
484 std::chrono::milliseconds(nTimeoutMS
),
485 [pMutex
]() { return pMutex
->m_wakeUpMain
; });
487 // here no need to check m_Request because Acquire will do it
493 bool SvpSalInstance::DoYield(bool bWait
, bool bHandleAllCurrentEvents
)
495 DBG_TESTSVPYIELDMUTEX();
496 DBG_TESTSOLARMUTEX();
498 bool bWasEvent(false);
499 SvpSalYieldMutex
*const pMutex(static_cast<SvpSalYieldMutex
*>(GetYieldMutex()));
503 bWasEvent
= ImplYield(bWait
, bHandleAllCurrentEvents
);
505 pMutex
->m_NonMainWaitingYieldCond
.set(); // wake up other threads
509 // TODO: use a SolarMutexReleaser here and drop the m_bNoYieldLock usage
510 Wakeup(bHandleAllCurrentEvents
511 ? SvpRequest::MainThreadDispatchAllEvents
512 : SvpRequest::MainThreadDispatchOneEvent
);
514 // blocking read (for synchronisation)
516 std::unique_lock
lock(pMutex
->m_FeedbackMutex
);
517 pMutex
->m_FeedbackCV
.wait(lock
, [pMutex
] { return !pMutex
->m_FeedbackPipe
.empty(); });
518 bWasEvent
= pMutex
->m_FeedbackPipe
.front();
519 pMutex
->m_FeedbackPipe
.pop();
521 if (!bWasEvent
&& bWait
)
523 // block & release YieldMutex until the main thread does something
524 pMutex
->m_NonMainWaitingYieldCond
.reset();
525 SolarMutexReleaser aReleaser
;
526 pMutex
->m_NonMainWaitingYieldCond
.wait();
533 bool SvpSalInstance::AnyInput( VclInputFlags nType
)
535 if( nType
& VclInputFlags::TIMER
)
536 return CheckTimeout( false );
540 OUString
SvpSalInstance::GetConnectionIdentifier()
545 void SvpSalInstance::StopTimer()
547 m_aTimeout
.tv_sec
= 0;
548 m_aTimeout
.tv_usec
= 0;
552 void SvpSalInstance::StartTimer( sal_uInt64 nMS
)
554 timeval
aPrevTimeout (m_aTimeout
);
555 gettimeofday (&m_aTimeout
, nullptr);
558 m_aTimeout
+= m_nTimeoutMS
;
560 if ((aPrevTimeout
> m_aTimeout
) || (aPrevTimeout
.tv_sec
== 0))
562 // Wakeup from previous timeout (or stopped timer).
567 void SvpSalInstance::AddToRecentDocumentList(const OUString
&, const OUString
&, const OUString
&)
571 SvpSalTimer::~SvpSalTimer()
575 void SvpSalTimer::Stop()
577 m_pInstance
->StopTimer();
580 void SvpSalTimer::Start( sal_uInt64 nMS
)
582 m_pInstance
->StartTimer( nMS
);
585 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */