/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 . */ #include #include #include #include #include #include #include #include #include #include #include #include #ifdef IOS # include # include # include #else # include # include #endif #include #include #include // FIXME: remove when we re-work the svp mainloop #include #include #include #include #if defined EMSCRIPTEN #include #endif SvpSalInstance* SvpSalInstance::s_pDefaultInstance = nullptr; #ifndef NDEBUG static bool g_CheckedMutex = false; #define DBG_TESTSVPYIELDMUTEX() \ do { \ if (!g_CheckedMutex) \ { \ assert(dynamic_cast(GetYieldMutex()) != nullptr \ && "This SvpSalInstance function requires use of SvpSalYieldMutex"); \ g_CheckedMutex = true; \ } \ } while(false) #else // NDEBUG #define DBG_TESTSVPYIELDMUTEX() ((void)0) #endif #if !defined(ANDROID) && !defined(IOS) && !defined(EMSCRIPTEN) static void atfork_child() { if (SvpSalInstance::s_pDefaultInstance != nullptr) { SvpSalInstance::s_pDefaultInstance->CloseWakeupPipe(); } } #endif SvpSalInstance::SvpSalInstance( std::unique_ptr pMutex ) : SalGenericInstance( std::move(pMutex) ) { m_aTimeout.tv_sec = 0; m_aTimeout.tv_usec = 0; m_nTimeoutMS = 0; m_MainThread = osl::Thread::getCurrentIdentifier(); if( s_pDefaultInstance == nullptr ) s_pDefaultInstance = this; #if !defined(ANDROID) && !defined(IOS) && !defined(EMSCRIPTEN) pthread_atfork(nullptr, nullptr, atfork_child); #endif #if defined EMSCRIPTEN ImplGetSVData()->maAppData.m_bUseSystemLoop = true; #endif } SvpSalInstance::~SvpSalInstance() { if( s_pDefaultInstance == this ) s_pDefaultInstance = nullptr; CloseWakeupPipe(); } void SvpSalInstance::CloseWakeupPipe() { SvpSalYieldMutex *const pMutex(dynamic_cast(GetYieldMutex())); if (!pMutex) return; while (!pMutex->m_FeedbackPipe.empty()) pMutex->m_FeedbackPipe.pop(); } void SvpSalInstance::TriggerUserEventProcessing() { Wakeup(); } void SvpSalInstance::Wakeup(SvpRequest const request) { DBG_TESTSVPYIELDMUTEX(); ImplSVData* pSVData = ImplGetSVData(); if (pSVData->mpWakeCallback && pSVData->mpPollClosure) pSVData->mpWakeCallback(pSVData->mpPollClosure); SvpSalYieldMutex *const pMutex(static_cast(GetYieldMutex())); std::scoped_lock g(pMutex->m_WakeUpMainMutex); if (request != SvpRequest::NONE) pMutex->m_Request = request; pMutex->m_wakeUpMain = true; pMutex->m_WakeUpMainCond.notify_one(); } bool SvpSalInstance::CheckTimeout( bool bExecuteTimers ) { bool bRet = false; if( m_aTimeout.tv_sec ) // timer is started { timeval aTimeOfDay; gettimeofday( &aTimeOfDay, nullptr ); if( aTimeOfDay >= m_aTimeout ) { bRet = true; if( bExecuteTimers ) { // timed out, update timeout m_aTimeout = aTimeOfDay; m_aTimeout += m_nTimeoutMS; osl::Guard< comphelper::SolarMutex > aGuard( GetYieldMutex() ); // notify ImplSVData* pSVData = ImplGetSVData(); if( pSVData->maSchedCtx.mpSalTimer ) pSVData->maSchedCtx.mpSalTimer->CallCallback(); } } } return bRet; } SalFrame* SvpSalInstance::CreateChildFrame( SystemParentData* /*pParent*/, SalFrameStyleFlags nStyle ) { return new SvpSalFrame( this, nullptr, nStyle ); } SalFrame* SvpSalInstance::CreateFrame( SalFrame* pParent, SalFrameStyleFlags nStyle ) { return new SvpSalFrame( this, pParent, nStyle ); } void SvpSalInstance::DestroyFrame( SalFrame* pFrame ) { delete pFrame; } SalObject* SvpSalInstance::CreateObject( SalFrame*, SystemWindowData*, bool ) { return new SvpSalObject; } void SvpSalInstance::DestroyObject( SalObject* pObject ) { delete pObject; } #ifndef IOS std::unique_ptr SvpSalInstance::CreateVirtualDevice(SalGraphics& rGraphics, tools::Long &nDX, tools::Long &nDY, DeviceFormat /*eFormat*/, const SystemGraphicsData* pGd) { SvpSalGraphics *pSvpSalGraphics = dynamic_cast(&rGraphics); assert(pSvpSalGraphics); #ifndef ANDROID // tdf#127529 normally pPreExistingTarget is null and we are a true virtualdevice drawing to a backing buffer. // Occasionally, for canvas/slideshow, pPreExistingTarget is pre-provided as a hack to use the vcl drawing // apis to render onto a preexisting cairo surface. The necessity for that precedes the use of cairo in vcl proper cairo_surface_t* pPreExistingTarget = pGd ? static_cast(pGd->pSurface) : nullptr; #else //ANDROID case (void)pGd; cairo_surface_t* pPreExistingTarget = nullptr; #endif std::unique_ptr xNew(new SvpSalVirtualDevice(pSvpSalGraphics->getSurface(), pPreExistingTarget)); if (!xNew->SetSize(nDX, nDY)) xNew.reset(); return xNew; } cairo_surface_t* get_underlying_cairo_surface(const VirtualDevice& rDevice) { return static_cast(rDevice.mpVirDev.get())->GetSurface(); } const cairo_font_options_t* SvpSalInstance::GetCairoFontOptions() { static cairo_font_options_t *gOptions = nullptr; if (!gOptions) { gOptions = cairo_font_options_create(); cairo_font_options_set_antialias(gOptions, CAIRO_ANTIALIAS_GRAY); } return gOptions; } #else // IOS const cairo_font_options_t* SvpSalInstance::GetCairoFontOptions() { return nullptr; } #endif SalTimer* SvpSalInstance::CreateSalTimer() { return new SvpSalTimer( this ); } SalSystem* SvpSalInstance::CreateSalSystem() { return new SvpSalSystem(); } std::shared_ptr SvpSalInstance::CreateSalBitmap() { #ifdef IOS return std::make_shared(); #else return std::make_shared(); #endif } void SvpSalInstance::ProcessEvent( SalUserEvent aEvent ) { DBG_TESTSVPYIELDMUTEX(); aEvent.m_pFrame->CallCallback( aEvent.m_nEvent, aEvent.m_pData ); if( aEvent.m_nEvent == SalEvent::Resize ) { // this would be a good time to post a paint const SvpSalFrame* pSvpFrame = static_cast( aEvent.m_pFrame); pSvpFrame->PostPaint(); } SvpSalYieldMutex *const pMutex(static_cast(GetYieldMutex())); pMutex->m_NonMainWaitingYieldCond.set(); } #if defined EMSCRIPTEN static void loop(void * arg) { SolarMutexGuard g; static_cast(arg)->ImplYield(false, false); } bool SvpSalInstance::DoExecute(int &) { assert(Application::IsOnSystemEventLoop()); // emscripten_set_main_loop will unwind the stack by throwing a JavaScript exception, so we need // to manually undo the call of AcquireYieldMutex() done in InitVCL: ReleaseYieldMutex(false); // Somewhat randomly use an fps=100 argument so the loop callback is called 100 times per // second: emscripten_set_main_loop_arg(loop, this, 100, 1); O3TL_UNREACHABLE; } void SvpSalInstance::DoQuit() { assert(Application::IsOnSystemEventLoop()); } #endif SvpSalYieldMutex::SvpSalYieldMutex() { } SvpSalYieldMutex::~SvpSalYieldMutex() { } void SvpSalYieldMutex::doAcquire(sal_uInt32 const nLockCount) { auto *const pInst = static_cast(GetSalInstance()); if (pInst && pInst->IsMainThread()) { if (m_bNoYieldLock) return; do { SvpRequest request = SvpRequest::NONE; { std::unique_lock g(m_WakeUpMainMutex); if (m_aMutex.tryToAcquire()) { // if there's a request, the other thread holds m_aMutex assert(m_Request == SvpRequest::NONE); m_wakeUpMain = false; break; } m_WakeUpMainCond.wait(g, [this]() { return m_wakeUpMain; }); m_wakeUpMain = false; std::swap(m_Request, request); } if (request != SvpRequest::NONE) { // nested Yield on behalf of another thread assert(!m_bNoYieldLock); m_bNoYieldLock = true; bool const bEvents = pInst->DoYield(false, request == SvpRequest::MainThreadDispatchAllEvents); m_bNoYieldLock = false; { std::lock_guard lock(m_FeedbackMutex); m_FeedbackPipe.push(bEvents); } m_FeedbackCV.notify_all(); } } while (true); } else { m_aMutex.acquire(); } ++m_nCount; SalYieldMutex::doAcquire(nLockCount - 1); } sal_uInt32 SvpSalYieldMutex::doRelease(bool const bUnlockAll) { auto *const pInst = static_cast(GetSalInstance()); if (pInst && pInst->IsMainThread()) { if (m_bNoYieldLock) return 1; else return SalYieldMutex::doRelease(bUnlockAll); } sal_uInt32 nCount; { // read m_nCount before doRelease bool const isReleased(bUnlockAll || m_nCount == 1); nCount = comphelper::SolarMutex::doRelease( bUnlockAll ); if (isReleased) { if (vcl::lok::isUnipoll()) { if (pInst) pInst->Wakeup(); } else { std::scoped_lock g(m_WakeUpMainMutex); m_wakeUpMain = true; m_WakeUpMainCond.notify_one(); } } } return nCount; } bool SvpSalYieldMutex::IsCurrentThread() const { if (GetSalInstance()->IsMainThread() && m_bNoYieldLock) return true; else return SalYieldMutex::IsCurrentThread(); } bool SvpSalInstance::IsMainThread() const { return osl::Thread::getCurrentIdentifier() == m_MainThread; } void SvpSalInstance::updateMainThread() { if (!IsMainThread()) { m_MainThread = osl::Thread::getCurrentIdentifier(); ImplGetSVData()->mnMainThreadId = osl::Thread::getCurrentIdentifier(); } } bool SvpSalInstance::ImplYield(bool bWait, bool bHandleAllCurrentEvents) { DBG_TESTSVPYIELDMUTEX(); DBG_TESTSOLARMUTEX(); assert(IsMainThread()); bool bWasEvent = DispatchUserEvents(bHandleAllCurrentEvents); if (!bHandleAllCurrentEvents && bWasEvent) return true; // CheckTimeout() invokes the sal timer, which invokes the scheduler. bWasEvent = CheckTimeout() || bWasEvent; const bool bMustSleep = bWait && !bWasEvent; // This is wrong and must be removed! // We always want to drop the SolarMutex on yield; that is the whole point of yield. // If we know the LOK client has pending input events, then don't yet return, so those events // can be processed as well. if (!bMustSleep && !comphelper::LibreOfficeKit::anyInput()) return bWasEvent; sal_Int64 nTimeoutMicroS = 0; if (bMustSleep) { if (m_aTimeout.tv_sec) // Timer is started. { timeval Timeout; // determine remaining timeout. gettimeofday (&Timeout, nullptr); if (m_aTimeout > Timeout) nTimeoutMicroS = ((m_aTimeout.tv_sec - Timeout.tv_sec) * 1000 * 1000 + (m_aTimeout.tv_usec - Timeout.tv_usec)); } else nTimeoutMicroS = -1; // wait until something happens } SolarMutexReleaser aReleaser; if (vcl::lok::isUnipoll()) { ImplSVData* pSVData = ImplGetSVData(); if (pSVData->mpPollClosure) { int nPollResult = pSVData->mpPollCallback(pSVData->mpPollClosure, nTimeoutMicroS); if (nPollResult < 0) pSVData->maAppData.mbAppQuit = true; bWasEvent = bWasEvent || (nPollResult != 0); } } else if (bMustSleep) { SvpSalYieldMutex *const pMutex(static_cast(GetYieldMutex())); std::unique_lock g(pMutex->m_WakeUpMainMutex); // wait for doRelease() or Wakeup() to set the condition if (nTimeoutMicroS == -1) { pMutex->m_WakeUpMainCond.wait(g, [pMutex]() { return pMutex->m_wakeUpMain; }); } else { int nTimeoutMS = nTimeoutMicroS / 1000; if (nTimeoutMicroS % 1000) nTimeoutMS += 1; pMutex->m_WakeUpMainCond.wait_for(g, std::chrono::milliseconds(nTimeoutMS), [pMutex]() { return pMutex->m_wakeUpMain; }); } // here no need to check m_Request because Acquire will do it } return bWasEvent; } bool SvpSalInstance::DoYield(bool bWait, bool bHandleAllCurrentEvents) { DBG_TESTSVPYIELDMUTEX(); DBG_TESTSOLARMUTEX(); bool bWasEvent(false); SvpSalYieldMutex *const pMutex(static_cast(GetYieldMutex())); if (IsMainThread()) { bWasEvent = ImplYield(bWait, bHandleAllCurrentEvents); if (bWasEvent) pMutex->m_NonMainWaitingYieldCond.set(); // wake up other threads } else { // TODO: use a SolarMutexReleaser here and drop the m_bNoYieldLock usage Wakeup(bHandleAllCurrentEvents ? SvpRequest::MainThreadDispatchAllEvents : SvpRequest::MainThreadDispatchOneEvent); // blocking read (for synchronisation) { std::unique_lock lock(pMutex->m_FeedbackMutex); pMutex->m_FeedbackCV.wait(lock, [pMutex] { return !pMutex->m_FeedbackPipe.empty(); }); bWasEvent = pMutex->m_FeedbackPipe.front(); pMutex->m_FeedbackPipe.pop(); } if (!bWasEvent && bWait) { // block & release YieldMutex until the main thread does something pMutex->m_NonMainWaitingYieldCond.reset(); SolarMutexReleaser aReleaser; pMutex->m_NonMainWaitingYieldCond.wait(); } } return bWasEvent; } bool SvpSalInstance::AnyInput( VclInputFlags nType ) { if( nType & VclInputFlags::TIMER ) return CheckTimeout( false ); return false; } OUString SvpSalInstance::GetConnectionIdentifier() { return OUString(); } void SvpSalInstance::StopTimer() { m_aTimeout.tv_sec = 0; m_aTimeout.tv_usec = 0; m_nTimeoutMS = 0; } void SvpSalInstance::StartTimer( sal_uInt64 nMS ) { timeval aPrevTimeout (m_aTimeout); gettimeofday (&m_aTimeout, nullptr); m_nTimeoutMS = nMS; m_aTimeout += m_nTimeoutMS; if ((aPrevTimeout > m_aTimeout) || (aPrevTimeout.tv_sec == 0)) { // Wakeup from previous timeout (or stopped timer). Wakeup(); } } void SvpSalInstance::AddToRecentDocumentList(const OUString&, const OUString&, const OUString&) { } SvpSalTimer::~SvpSalTimer() { } void SvpSalTimer::Stop() { m_pInstance->StopTimer(); } void SvpSalTimer::Start( sal_uInt64 nMS ) { m_pInstance->StartTimer( nMS ); } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */