This is an automated email from the ASF dual-hosted git repository. jimjag pushed a commit to branch AOO50X in repository https://gitbox.apache.org/repos/asf/openoffice.git
commit 33d22be1ab634eb5ed0cbab2d3104438b847f927 Author: Jim Jagielski <[email protected]> AuthorDate: Thu Oct 22 08:01:51 2026 -0400 macOS: run the AppKit menu and window calls from UNO threads on AOO's main thread macOS AppKit throws or blocks on menu and window changes made off the main thread, so remote UNO clients crashed soffice on load and close, and failed on the first edit. (cherry picked from commit 2a4643a67e92c4338a0f7b947e78b04714015a0d) --- main/vcl/aqua/source/app/salinst.cxx | 225 ++++++++++++++++++++++++++++--- main/vcl/aqua/source/window/salframe.cxx | 68 ++++++++++ main/vcl/aqua/source/window/salmenu.cxx | 74 +++++++++- main/vcl/inc/aqua/salinst.h | 46 +++++++ 4 files changed, 393 insertions(+), 20 deletions(-) diff --git a/main/vcl/aqua/source/app/salinst.cxx b/main/vcl/aqua/source/app/salinst.cxx index 0cc37adeb7..1d2288e3f3 100644 --- a/main/vcl/aqua/source/app/salinst.cxx +++ b/main/vcl/aqua/source/app/salinst.cxx @@ -25,6 +25,7 @@ #include "precompiled_vcl.hxx" #include <stdio.h> +#include <utility> #include "tools/fsys.hxx" #include "tools/getprocessworkingdir.hxx" @@ -339,21 +340,159 @@ void DeInitSalMain() // ======================================================================= +static void postYieldWakeupEvent() +{ + if( ! NSApp ) + return; + SalData::ensureThreadAutoreleasePool(); + NSEvent* pEvent = [NSEvent otherEventWithType: NSApplicationDefined + location: NSZeroPoint + modifierFlags: 0 + timestamp: 0 + windowNumber: 0 + context: nil + subtype: AquaSalInstance::YieldWakeupEvent + data1: 0 + data2: 0 ]; + if( pEvent ) + [NSApp postEvent: pEvent atStart: NO]; +} + +@interface SalMainThreadRunner : NSObject +{ +} +-(void)runPending: (id)pUnused; +@end + +@implementation SalMainThreadRunner +-(void)runPending: (id)pUnused +{ + (void)pUnused; + GetSalData()->mpFirstInstance->mpSalYieldMutex->runPendingOnMainThread(); +} +@end + SalYieldMutex::SalYieldMutex() { mnCount = 0; mnThreadId = 0; + mnMainThreadId = 0; + pthread_mutex_init( &maMainThreadMutex, NULL ); + pthread_cond_init( &maMainThreadCond, NULL ); + pthread_cond_init( &maResultCond, NULL ); + mbWakeUpMain = false; + mbMainWaiting = false; + mbResultReady = false; + mpMainThreadFunc = NULL; + mbRunningForOwner = false; +} + +SalYieldMutex::~SalYieldMutex() +{ + pthread_cond_destroy( &maResultCond ); + pthread_cond_destroy( &maMainThreadCond ); + pthread_mutex_destroy( &maMainThreadMutex ); +} + +bool SalYieldMutex::isMainThread() const +{ + return mnMainThreadId != 0 && vos::OThread::getCurrentIdentifier() == mnMainThreadId; } void SalYieldMutex::acquire() { - OMutex::acquire(); + if( isMainThread() ) + { + if( mbRunningForOwner ) + return; + acquireOnMainThread(); + } + else + OMutex::acquire(); mnThreadId = vos::OThread::getCurrentIdentifier(); mnCount++; } +void SalYieldMutex::acquireOnMainThread() +{ + // while another thread owns the mutex, run what it hands over via runOnMainThread() + for(;;) + { + pthread_mutex_lock( &maMainThreadMutex ); + if( OMutex::tryToAcquire() ) + { + mbWakeUpMain = false; + pthread_mutex_unlock( &maMainThreadMutex ); + return; + } + mbMainWaiting = true; + while( ! mbWakeUpMain ) + pthread_cond_wait( &maMainThreadCond, &maMainThreadMutex ); + mbMainWaiting = false; + mbWakeUpMain = false; + const boost::function0<void>* pFunc = mpMainThreadFunc; + mpMainThreadFunc = NULL; + pthread_mutex_unlock( &maMainThreadMutex ); + + if( pFunc ) + runForOwner( pFunc ); + } +} + +void SalYieldMutex::runPendingOnMainThread() +{ + if( ! isMainThread() || mbRunningForOwner ) + return; + + pthread_mutex_lock( &maMainThreadMutex ); + const boost::function0<void>* pFunc = mpMainThreadFunc; + mpMainThreadFunc = NULL; + pthread_mutex_unlock( &maMainThreadMutex ); + + if( pFunc ) + runForOwner( pFunc ); +} + +void SalYieldMutex::runForOwner( const boost::function0<void>* pFunc ) +{ + std::exception_ptr aException; + mbRunningForOwner = true; + try + { + (*pFunc)(); + } + catch( ... ) + { + aException = std::current_exception(); + } + mbRunningForOwner = false; + + pthread_mutex_lock( &maMainThreadMutex ); + maMainThreadException = aException; + mbResultReady = true; + pthread_cond_signal( &maResultCond ); + pthread_mutex_unlock( &maMainThreadMutex ); +} + void SalYieldMutex::release() { + const bool bMainThread = isMainThread(); + if( bMainThread && mbRunningForOwner ) + return; + + // the main thread may be waiting for this release in acquireOnMainThread() + if( ! bMainThread && mnCount == 1 && mnThreadId == vos::OThread::getCurrentIdentifier() ) + { + pthread_mutex_lock( &maMainThreadMutex ); + mnThreadId = 0; + mnCount--; + OMutex::release(); + mbWakeUpMain = true; + pthread_cond_signal( &maMainThreadCond ); + pthread_mutex_unlock( &maMainThreadMutex ); + return; + } + if ( mnThreadId == vos::OThread::getCurrentIdentifier() ) { if ( mnCount == 1 ) @@ -363,8 +502,56 @@ void SalYieldMutex::release() OMutex::release(); } +void SalYieldMutex::runOnMainThread( const boost::function0<void>& rFunc ) +{ + if( isMainThread() ) + { + rFunc(); + return; + } + + // the main thread only runs rFunc for the owner of the mutex + vos::OGuard aGuard( *this ); + + bool bMainWaiting; + pthread_mutex_lock( &maMainThreadMutex ); + mpMainThreadFunc = &rFunc; + mbWakeUpMain = true; + bMainWaiting = mbMainWaiting; + pthread_cond_signal( &maMainThreadCond ); + pthread_mutex_unlock( &maMainThreadMutex ); + + // common modes also reach a main thread inside AppKit's menu tracking or live resize loops + if( ! bMainWaiting ) + { + static SalMainThreadRunner* pRunner = [[SalMainThreadRunner alloc] init]; + [pRunner performSelectorOnMainThread: @selector(runPending:) + withObject: nil + waitUntilDone: NO + modes: [NSArray arrayWithObject: NSRunLoopCommonModes]]; + } + + std::exception_ptr aException; + pthread_mutex_lock( &maMainThreadMutex ); + while( ! mbResultReady ) + pthread_cond_wait( &maResultCond, &maMainThreadMutex ); + mbResultReady = false; + std::swap( aException, maMainThreadException ); + pthread_mutex_unlock( &maMainThreadMutex ); + + if( aException ) + std::rethrow_exception( aException ); +} + +bool SalYieldMutex::isRunningForOwner() const +{ + return isMainThread() && mbRunningForOwner; +} + sal_Bool SalYieldMutex::tryToAcquire() { + if( isMainThread() && mbRunningForOwner ) + return sal_True; if ( OMutex::tryToAcquire() ) { mnThreadId = vos::OThread::getCurrentIdentifier(); @@ -444,10 +631,11 @@ void DestroySalInstance( SalInstance* pInst ) AquaSalInstance::AquaSalInstance() { + maMainThread = vos::OThread::getCurrentIdentifier(); mpSalYieldMutex = new SalYieldMutex; + mpSalYieldMutex->setMainThread( maMainThread ); mpSalYieldMutex->acquire(); ::tools::SolarMutex::SetSolarMutex( mpSalYieldMutex ); - maMainThread = vos::OThread::getCurrentIdentifier(); mbWaitingYield = false; maUserEventListMutex = osl_createMutex(); mnActivePrintJobs = 0; @@ -471,20 +659,7 @@ void AquaSalInstance::wakeupYield() { // wakeup :Yield if( mbWaitingYield ) - { - SalData::ensureThreadAutoreleasePool(); - NSEvent* pEvent = [NSEvent otherEventWithType: NSApplicationDefined - location: NSZeroPoint - modifierFlags: 0 - timestamp: 0 - windowNumber: 0 - context: nil - subtype: AquaSalInstance::YieldWakeupEvent - data1: 0 - data2: 0 ]; - if( pEvent ) - [NSApp postEvent: pEvent atStart: NO]; - } + postYieldWakeupEvent(); } // ----------------------------------------------------------------------- @@ -546,6 +721,9 @@ bool AquaSalInstance::CheckYieldMutex() { bool bRet = true; + if( mpSalYieldMutex->isRunningForOwner() ) + return bRet; + SalYieldMutex* pYieldMutex = mpSalYieldMutex; if ( pYieldMutex->GetThreadId() != vos::OThread::getCurrentIdentifier() ) @@ -565,6 +743,16 @@ bool AquaSalInstance::isNSAppThread() const // ----------------------------------------------------------------------- +bool AquaSalInstance::forwardToMainThread( const boost::function0<void>& rFunc ) +{ + if( isNSAppThread() ) + return false; + mpSalYieldMutex->runOnMainThread( rFunc ); + return true; +} + +// ----------------------------------------------------------------------- + void AquaSalInstance::handleAppDefinedEvent( NSEvent* pEvent ) { switch( [pEvent subtype] ) @@ -820,6 +1008,9 @@ void AquaSalInstance::Yield( bool bWait, bool bHandleAllCurrentEvents ) bool AquaSalInstance::AnyInput( sal_uInt16 nType ) { + if( ! isNSAppThread() ) + return callOnMainThread< bool >( boost::bind( &AquaSalInstance::AnyInput, this, nType ) ); + if( nType & INPUT_APPEVENT ) { if( ! aAppEventList.empty() ) @@ -883,6 +1074,8 @@ SalFrame* AquaSalInstance::CreateFrame( SalFrame* pParent, sal_uLong nSalFrameSt void AquaSalInstance::DestroyFrame( SalFrame* pFrame ) { + if( forwardToMainThread( boost::bind( &AquaSalInstance::DestroyFrame, this, pFrame ) ) ) + return; delete pFrame; } diff --git a/main/vcl/aqua/source/window/salframe.cxx b/main/vcl/aqua/source/window/salframe.cxx index 8638e68e8b..af99c4c153 100644 --- a/main/vcl/aqua/source/window/salframe.cxx +++ b/main/vcl/aqua/source/window/salframe.cxx @@ -301,6 +301,9 @@ sal_Bool AquaSalFrame::PostEvent( void *pData ) // ----------------------------------------------------------------------- void AquaSalFrame::SetTitle(const XubString& rTitle) { + if( GetSalData()->mpFirstInstance->forwardToMainThread( boost::bind( &AquaSalFrame::SetTitle, this, boost::cref( rTitle ) ) ) ) + return; + if ( !mpNSWindow ) return; @@ -345,6 +348,9 @@ void AquaSalFrame::SetIcon( sal_uInt16 ) void AquaSalFrame::SetRepresentedURL( const rtl::OUString& i_rDocURL ) { + if( GetSalData()->mpFirstInstance->forwardToMainThread( boost::bind( &AquaSalFrame::SetRepresentedURL, this, boost::cref( i_rDocURL ) ) ) ) + return; + // #i113170# may not be the main thread if called from UNO API SalData::ensureThreadAutoreleasePool(); @@ -418,6 +424,9 @@ void AquaSalFrame::SendPaintEvent( const Rectangle* pRect ) void AquaSalFrame::Show(sal_Bool bVisible, sal_Bool bNoActivate) { + if( GetSalData()->mpFirstInstance->forwardToMainThread( boost::bind( &AquaSalFrame::Show, this, bVisible, bNoActivate ) ) ) + return; + if ( !mpNSWindow ) return; @@ -487,6 +496,9 @@ void AquaSalFrame::Enable( sal_Bool ) void AquaSalFrame::SetMinClientSize( long nWidth, long nHeight ) { + if( GetSalData()->mpFirstInstance->forwardToMainThread( boost::bind( &AquaSalFrame::SetMinClientSize, this, nWidth, nHeight ) ) ) + return; + // #i113170# may not be the main thread if called from UNO API SalData::ensureThreadAutoreleasePool(); @@ -512,6 +524,9 @@ void AquaSalFrame::SetMinClientSize( long nWidth, long nHeight ) void AquaSalFrame::SetMaxClientSize( long nWidth, long nHeight ) { + if( GetSalData()->mpFirstInstance->forwardToMainThread( boost::bind( &AquaSalFrame::SetMaxClientSize, this, nWidth, nHeight ) ) ) + return; + // #i113170# may not be the main thread if called from UNO API SalData::ensureThreadAutoreleasePool(); @@ -541,6 +556,9 @@ void AquaSalFrame::SetMaxClientSize( long nWidth, long nHeight ) void AquaSalFrame::SetClientSize( long nWidth, long nHeight ) { + if( GetSalData()->mpFirstInstance->forwardToMainThread( boost::bind( &AquaSalFrame::SetClientSize, this, nWidth, nHeight ) ) ) + return; + // #i113170# may not be the main thread if called from UNO API SalData::ensureThreadAutoreleasePool(); @@ -576,6 +594,9 @@ void AquaSalFrame::GetClientSize( long& rWidth, long& rHeight ) void AquaSalFrame::SetWindowState( const SalFrameState* pState ) { + if( GetSalData()->mpFirstInstance->forwardToMainThread( boost::bind( &AquaSalFrame::SetWindowState, this, pState ) ) ) + return; + // #i113170# may not be the main thread if called from UNO API SalData::ensureThreadAutoreleasePool(); @@ -652,6 +673,10 @@ void AquaSalFrame::SetWindowState( const SalFrameState* pState ) sal_Bool AquaSalFrame::GetWindowState( SalFrameState* pState ) { + AquaSalInstance* pInst = GetSalData()->mpFirstInstance; + if( ! pInst->isNSAppThread() ) + return pInst->callOnMainThread< sal_Bool >( boost::bind( &AquaSalFrame::GetWindowState, this, pState ) ); + if ( !mpNSWindow ) return FALSE; @@ -692,6 +717,9 @@ sal_Bool AquaSalFrame::GetWindowState( SalFrameState* pState ) void AquaSalFrame::SetScreenNumber(unsigned int nScreen) { + if( GetSalData()->mpFirstInstance->forwardToMainThread( boost::bind( &AquaSalFrame::SetScreenNumber, this, nScreen ) ) ) + return; + if ( !mpNSWindow ) return; @@ -729,6 +757,9 @@ void AquaSalFrame::SetScreenNumber(unsigned int nScreen) void AquaSalFrame::ShowFullScreen( sal_Bool bFullScreen, sal_Int32 nDisplay ) { + if( GetSalData()->mpFirstInstance->forwardToMainThread( boost::bind( &AquaSalFrame::ShowFullScreen, this, bFullScreen, nDisplay ) ) ) + return; + if ( !mpNSWindow ) return; @@ -842,6 +873,9 @@ public: void AquaSalFrame::StartPresentation( sal_Bool bStart ) { + if( GetSalData()->mpFirstInstance->forwardToMainThread( boost::bind( &AquaSalFrame::StartPresentation, this, bStart ) ) ) + return; + if ( !mpNSWindow ) return; @@ -874,6 +908,9 @@ void AquaSalFrame::SetAlwaysOnTop( sal_Bool ) void AquaSalFrame::ToTop(sal_uInt16 nFlags) { + if( GetSalData()->mpFirstInstance->forwardToMainThread( boost::bind( &AquaSalFrame::ToTop, this, nFlags ) ) ) + return; + if ( !mpNSWindow ) return; @@ -936,6 +973,9 @@ NSCursor* AquaSalFrame::getCurrentCursor() const void AquaSalFrame::SetPointer( PointerStyle ePointerStyle ) { + if( GetSalData()->mpFirstInstance->forwardToMainThread( boost::bind( &AquaSalFrame::SetPointer, this, ePointerStyle ) ) ) + return; + if ( !mpNSWindow ) return; @@ -1237,6 +1277,9 @@ void AquaSalFrame::getResolution( sal_Int32& o_rDPIX, sal_Int32& o_rDPIY ) // doesn't make the anything cleaner for now void AquaSalFrame::UpdateSettings( AllSettings& rSettings ) { + if( GetSalData()->mpFirstInstance->forwardToMainThread( boost::bind( &AquaSalFrame::UpdateSettings, this, boost::ref( rSettings ) ) ) ) + return; + if ( !mpNSWindow ) return; @@ -1354,6 +1397,9 @@ void AquaSalFrame::Beep( SoundType eSoundType ) void AquaSalFrame::SetPosSize(long nX, long nY, long nWidth, long nHeight, sal_uInt16 nFlags) { + if( GetSalData()->mpFirstInstance->forwardToMainThread( boost::bind( &AquaSalFrame::SetPosSize, this, nX, nY, nWidth, nHeight, nFlags ) ) ) + return; + if ( !mpNSWindow ) return; @@ -1444,6 +1490,9 @@ void AquaSalFrame::SetPosSize(long nX, long nY, long nWidth, long nHeight, sal_u void AquaSalFrame::GetWorkArea( Rectangle& rRect ) { + if( GetSalData()->mpFirstInstance->forwardToMainThread( boost::bind( &AquaSalFrame::GetWorkArea, this, boost::ref( rRect ) ) ) ) + return; + if ( !mpNSWindow ) return; @@ -1463,6 +1512,10 @@ void AquaSalFrame::GetWorkArea( Rectangle& rRect ) SalPointerState AquaSalFrame::GetPointerState() { + AquaSalInstance* pInst = GetSalData()->mpFirstInstance; + if( ! pInst->isNSAppThread() ) + return pInst->callOnMainThread< SalPointerState >( boost::bind( &AquaSalFrame::GetPointerState, this ) ); + // #i113170# may not be the main thread if called from UNO API SalData::ensureThreadAutoreleasePool(); @@ -1573,6 +1626,9 @@ void AquaSalFrame::DrawMenuBar() void AquaSalFrame::SetMenu( SalMenu* pSalMenu ) { + if( GetSalData()->mpFirstInstance->forwardToMainThread( boost::bind( &AquaSalFrame::SetMenu, this, pSalMenu ) ) ) + return; + // #i113170# may not be the main thread if called from UNO API SalData::ensureThreadAutoreleasePool(); @@ -1585,6 +1641,9 @@ void AquaSalFrame::SetMenu( SalMenu* pSalMenu ) void AquaSalFrame::SetExtendedFrameStyle( SalExtStyle nStyle ) { + if( GetSalData()->mpFirstInstance->forwardToMainThread( boost::bind( &AquaSalFrame::SetExtendedFrameStyle, this, nStyle ) ) ) + return; + if ( mpNSWindow ) { // #i113170# may not be the main thread if called from UNO API @@ -1701,6 +1760,9 @@ void AquaSalFrame::CaptureMouse( sal_Bool bCapture ) void AquaSalFrame::ResetClipRegion() { + if( GetSalData()->mpFirstInstance->forwardToMainThread( boost::bind( &AquaSalFrame::ResetClipRegion, this ) ) ) + return; + if ( !mpNSWindow ) { return; @@ -1724,6 +1786,9 @@ void AquaSalFrame::ResetClipRegion() void AquaSalFrame::BeginSetClipRegion( sal_uLong nRects ) { + if( GetSalData()->mpFirstInstance->forwardToMainThread( boost::bind( &AquaSalFrame::BeginSetClipRegion, this, nRects ) ) ) + return; + if ( !mpNSWindow ) { return; @@ -1763,6 +1828,9 @@ void AquaSalFrame::UnionClipRegion( long nX, long nY, long nWidth, long nHeight void AquaSalFrame::EndSetClipRegion() { + if( GetSalData()->mpFirstInstance->forwardToMainThread( boost::bind( &AquaSalFrame::EndSetClipRegion, this ) ) ) + return; + if ( !mpNSWindow ) { return; diff --git a/main/vcl/aqua/source/window/salmenu.cxx b/main/vcl/aqua/source/window/salmenu.cxx index 5f66d7e460..a33db1f963 100644 --- a/main/vcl/aqua/source/window/salmenu.cxx +++ b/main/vcl/aqua/source/window/salmenu.cxx @@ -98,6 +98,8 @@ static MainMenuSelector* pMainMenuSelector = nil; static void initAppMenu() { static bool bOnce = true; + if( bOnce && GetSalData()->mpFirstInstance->forwardToMainThread( &initAppMenu ) ) + return; if( bOnce ) { bOnce = false; @@ -209,6 +211,9 @@ static void initAppMenu() SalMenu* AquaSalInstance::CreateMenu( sal_Bool bMenuBar, Menu* pVCLMenu ) { + if( ! isNSAppThread() ) + return callOnMainThread< SalMenu* >( boost::bind( &AquaSalInstance::CreateMenu, this, bMenuBar, pVCLMenu ) ); + initAppMenu(); AquaSalMenu *pAquaSalMenu = new AquaSalMenu( bMenuBar ); @@ -219,11 +224,16 @@ SalMenu* AquaSalInstance::CreateMenu( sal_Bool bMenuBar, Menu* pVCLMenu ) void AquaSalInstance::DestroyMenu( SalMenu* pSalMenu ) { + if( forwardToMainThread( boost::bind( &AquaSalInstance::DestroyMenu, this, pSalMenu ) ) ) + return; delete pSalMenu; } SalMenuItem* AquaSalInstance::CreateMenuItem( const SalItemParams* pItemData ) { + if( ! isNSAppThread() ) + return callOnMainThread< SalMenuItem* >( boost::bind( &AquaSalInstance::CreateMenuItem, this, pItemData ) ); + if( !pItemData ) return NULL; @@ -234,6 +244,8 @@ SalMenuItem* AquaSalInstance::CreateMenuItem( const SalItemParams* pItemData ) void AquaSalInstance::DestroyMenuItem( SalMenuItem* pSalMenuItem ) { + if( forwardToMainThread( boost::bind( &AquaSalInstance::DestroyMenuItem, this, pSalMenuItem ) ) ) + return; delete pSalMenuItem; } @@ -341,6 +353,10 @@ sal_Int32 removeUnusedItemsRunner(NSMenu * pMenu) bool AquaSalMenu::ShowNativePopupMenu(FloatingWindow * pWin, const Rectangle& rRect, sal_uLong nFlags) { + AquaSalInstance* pInst = GetSalData()->mpFirstInstance; + if( ! pInst->isNSAppThread() ) + return pInst->callOnMainThread< bool >( boost::bind( &AquaSalMenu::ShowNativePopupMenu, this, pWin, boost::cref( rRect ), nFlags ) ); + // do not use native popup menu when AQUA_NATIVE_MENUS is set to sal_False if( ! VisibleMenuBar() ) { return false; @@ -423,6 +439,9 @@ void AquaSalMenu::unsetMainMenu() void AquaSalMenu::setMainMenu() { + if( GetSalData()->mpFirstInstance->forwardToMainThread( boost::bind( &AquaSalMenu::setMainMenu, this ) ) ) + return; + DBG_ASSERT( mbMenuBar, "setMainMenu on non menubar" ); if( mbMenuBar ) { @@ -446,6 +465,9 @@ void AquaSalMenu::setMainMenu() void AquaSalMenu::setDefaultMenu() { + if( GetSalData()->mpFirstInstance->forwardToMainThread( &AquaSalMenu::setDefaultMenu ) ) + return; + NSMenu* pMenu = [NSApp mainMenu]; unsetMainMenu(); @@ -462,6 +484,9 @@ void AquaSalMenu::setDefaultMenu() void AquaSalMenu::enableMainMenu( bool bEnable ) { + if( GetSalData()->mpFirstInstance->forwardToMainThread( boost::bind( &AquaSalMenu::enableMainMenu, bEnable ) ) ) + return; + NSMenu* pMainMenu = [NSApp mainMenu]; if( pMainMenu ) { @@ -477,6 +502,9 @@ void AquaSalMenu::enableMainMenu( bool bEnable ) void AquaSalMenu::addFallbackMenuItem( NSMenuItem* pNewItem ) { + if( GetSalData()->mpFirstInstance->forwardToMainThread( boost::bind( &AquaSalMenu::addFallbackMenuItem, pNewItem ) ) ) + return; + initAppMenu(); std::vector< NSMenuItem* >& rFallbackMenu( GetSalData()->maFallbackMenu ); @@ -499,6 +527,9 @@ void AquaSalMenu::addFallbackMenuItem( NSMenuItem* pNewItem ) void AquaSalMenu::removeFallbackMenuItem( NSMenuItem* pOldItem ) { + if( GetSalData()->mpFirstInstance->forwardToMainThread( boost::bind( &AquaSalMenu::removeFallbackMenuItem, pOldItem ) ) ) + return; + std::vector< NSMenuItem* >& rFallbackMenu( GetSalData()->maFallbackMenu ); // find item @@ -542,6 +573,9 @@ void AquaSalMenu::SetFrame( const SalFrame *pFrame ) void AquaSalMenu::InsertItem( SalMenuItem* pSalMenuItem, unsigned nPos ) { + if( GetSalData()->mpFirstInstance->forwardToMainThread( boost::bind( &AquaSalMenu::InsertItem, this, pSalMenuItem, nPos ) ) ) + return; + AquaSalMenuItem *pAquaSalMenuItem = static_cast<AquaSalMenuItem*>(pSalMenuItem); pAquaSalMenuItem->mpParentMenu = this; @@ -568,6 +602,9 @@ void AquaSalMenu::InsertItem( SalMenuItem* pSalMenuItem, unsigned nPos ) void AquaSalMenu::RemoveItem( unsigned nPos ) { + if( GetSalData()->mpFirstInstance->forwardToMainThread( boost::bind( &AquaSalMenu::RemoveItem, this, nPos ) ) ) + return; + AquaSalMenuItem* pRemoveItem = NULL; if( nPos == MENU_APPEND || nPos == (maItems.size()-1) ) { @@ -591,8 +628,11 @@ void AquaSalMenu::RemoveItem( unsigned nPos ) [mpMenu removeItemAtIndex: getItemIndexByPos(nPos)]; } -void AquaSalMenu::SetSubMenu( SalMenuItem* pSalMenuItem, SalMenu* pSubMenu, unsigned /*nPos*/ ) +void AquaSalMenu::SetSubMenu( SalMenuItem* pSalMenuItem, SalMenu* pSubMenu, unsigned nPos ) { + if( GetSalData()->mpFirstInstance->forwardToMainThread( boost::bind( &AquaSalMenu::SetSubMenu, this, pSalMenuItem, pSubMenu, nPos ) ) ) + return; + AquaSalMenuItem *pAquaSalMenuItem = static_cast<AquaSalMenuItem*>(pSalMenuItem); AquaSalMenu *subAquaSalMenu = static_cast<AquaSalMenu*>(pSubMenu); @@ -633,6 +673,9 @@ void AquaSalMenu::SetSubMenu( SalMenuItem* pSalMenuItem, SalMenu* pSubMenu, unsi void AquaSalMenu::CheckItem( unsigned nPos, sal_Bool bCheck ) { + if( GetSalData()->mpFirstInstance->forwardToMainThread( boost::bind( &AquaSalMenu::CheckItem, this, nPos, bCheck ) ) ) + return; + if( nPos < maItems.size() ) { NSMenuItem* pItem = maItems[nPos]->mpMenuItem; @@ -642,6 +685,9 @@ void AquaSalMenu::CheckItem( unsigned nPos, sal_Bool bCheck ) void AquaSalMenu::EnableItem( unsigned nPos, sal_Bool bEnable ) { + if( GetSalData()->mpFirstInstance->forwardToMainThread( boost::bind( &AquaSalMenu::EnableItem, this, nPos, bEnable ) ) ) + return; + if( nPos < maItems.size() ) { NSMenuItem* pItem = maItems[nPos]->mpMenuItem; @@ -649,8 +695,11 @@ void AquaSalMenu::EnableItem( unsigned nPos, sal_Bool bEnable ) } } -void AquaSalMenu::SetItemImage( unsigned /*nPos*/, SalMenuItem* pSMI, const Image& rImage ) +void AquaSalMenu::SetItemImage( unsigned nPos, SalMenuItem* pSMI, const Image& rImage ) { + if( GetSalData()->mpFirstInstance->forwardToMainThread( boost::bind( &AquaSalMenu::SetItemImage, this, nPos, pSMI, boost::cref( rImage ) ) ) ) + return; + AquaSalMenuItem* pSalMenuItem = static_cast<AquaSalMenuItem*>( pSMI ); if( ! pSalMenuItem || ! pSalMenuItem->mpMenuItem ) return; @@ -662,8 +711,11 @@ void AquaSalMenu::SetItemImage( unsigned /*nPos*/, SalMenuItem* pSMI, const Imag [pImage release]; } -void AquaSalMenu::SetItemText( unsigned /*i_nPos*/, SalMenuItem* i_pSalMenuItem, const XubString& i_rText ) +void AquaSalMenu::SetItemText( unsigned i_nPos, SalMenuItem* i_pSalMenuItem, const XubString& i_rText ) { + if( GetSalData()->mpFirstInstance->forwardToMainThread( boost::bind( &AquaSalMenu::SetItemText, this, i_nPos, i_pSalMenuItem, boost::cref( i_rText ) ) ) ) + return; + if (!i_pSalMenuItem) return; @@ -699,8 +751,11 @@ void AquaSalMenu::SetItemText( unsigned /*i_nPos*/, SalMenuItem* i_pSalMenuItem, } } -void AquaSalMenu::SetAccelerator( unsigned /*nPos*/, SalMenuItem* pSalMenuItem, const KeyCode& rKeyCode, const XubString& /*rKeyName*/ ) +void AquaSalMenu::SetAccelerator( unsigned nPos, SalMenuItem* pSalMenuItem, const KeyCode& rKeyCode, const XubString& rKeyName ) { + if( GetSalData()->mpFirstInstance->forwardToMainThread( boost::bind( &AquaSalMenu::SetAccelerator, this, nPos, pSalMenuItem, boost::cref( rKeyCode ), boost::cref( rKeyName ) ) ) ) + return; + sal_uInt16 nModifier; sal_Unicode nCommandKey = 0; @@ -824,6 +879,10 @@ void AquaSalMenu::releaseButtonEntry( MenuBarButtonEntry& i_rEntry ) bool AquaSalMenu::AddMenuBarButton( const SalMenuButtonItem& i_rNewItem ) { + AquaSalInstance* pInst = GetSalData()->mpFirstInstance; + if( ! pInst->isNSAppThread() ) + return pInst->callOnMainThread< bool >( boost::bind( &AquaSalMenu::AddMenuBarButton, this, boost::cref( i_rNewItem ) ) ); + if( ! mbMenuBar || ! VisibleMenuBar() ) return false; @@ -854,6 +913,9 @@ bool AquaSalMenu::AddMenuBarButton( const SalMenuButtonItem& i_rNewItem ) void AquaSalMenu::RemoveMenuBarButton( sal_uInt16 i_nId ) { + if( GetSalData()->mpFirstInstance->forwardToMainThread( boost::bind( &AquaSalMenu::RemoveMenuBarButton, this, i_nId ) ) ) + return; + MenuBarButtonEntry* pEntry = findButtonItem( i_nId ); if( pEntry ) { @@ -868,6 +930,10 @@ void AquaSalMenu::RemoveMenuBarButton( sal_uInt16 i_nId ) Rectangle AquaSalMenu::GetMenuBarButtonRectPixel( sal_uInt16 i_nItemId, SalFrame* i_pReferenceFrame ) { + AquaSalInstance* pInst = GetSalData()->mpFirstInstance; + if( ! pInst->isNSAppThread() ) + return pInst->callOnMainThread< Rectangle >( boost::bind( &AquaSalMenu::GetMenuBarButtonRectPixel, this, i_nItemId, i_pReferenceFrame ) ); + if( GetSalData()->mnSystemVersion < OSX_VER_LEOPARD ) return Rectangle( Point( -1, -1 ), Size( 1, 1 ) ); diff --git a/main/vcl/inc/aqua/salinst.h b/main/vcl/inc/aqua/salinst.h index 37a4569848..927e737dd4 100644 --- a/main/vcl/inc/aqua/salinst.h +++ b/main/vcl/inc/aqua/salinst.h @@ -32,7 +32,13 @@ #include "salinst.hxx" +#include <boost/function.hpp> +#include <boost/bind.hpp> +#include <boost/ref.hpp> + +#include <exception> #include <list> +#include <pthread.h> class AquaSalFrame; class ApplicationEvent; @@ -46,14 +52,39 @@ class SalYieldMutex : public vos::OMutex { sal_uLong mnCount; vos::OThread::TThreadIdentifier mnThreadId; + vos::OThread::TThreadIdentifier mnMainThreadId; + + // state for running code on the main thread on behalf of the owner + pthread_mutex_t maMainThreadMutex; + pthread_cond_t maMainThreadCond; + pthread_cond_t maResultCond; + bool mbWakeUpMain; + bool mbMainWaiting; + bool mbResultReady; + const boost::function0<void>* mpMainThreadFunc; + std::exception_ptr maMainThreadException; + bool mbRunningForOwner; // main thread only + + bool isMainThread() const; + void acquireOnMainThread(); + void runForOwner( const boost::function0<void>* pFunc ); public: SalYieldMutex(); + ~SalYieldMutex(); virtual void acquire(); virtual void release(); virtual sal_Bool tryToAcquire(); sal_uLong GetAcquireCount() const { return mnCount; } vos::OThread::TThreadIdentifier GetThreadId() const { return mnThreadId; } + + void setMainThread( vos::OThread::TThreadIdentifier nId ) { mnMainThreadId = nId; } + // runs rFunc on the main thread while the calling thread keeps owning the mutex + void runOnMainThread( const boost::function0<void>& rFunc ); + // true while the main thread runs code for the owner; the main thread then counts as owner + bool isRunningForOwner() const; + // runs a function handed over by runOnMainThread(), if any; main thread only + void runPendingOnMainThread(); }; #define YIELD_GUARD vos::OGuard aGuard( GetSalData()->mpFirstInstance->GetYieldMutex() ) @@ -157,6 +188,11 @@ public: void wakeupYield(); + template< typename R > static void assignResult( R* pResult, const boost::function0<R>& rFunc ) + { + *pResult = rFunc(); + } + public: friend class AquaSalFrame; @@ -165,6 +201,16 @@ public: bool isNSAppThread() const; + // AppKit raises or deadlocks when used off the main thread (e.g. by remote UNO calls): + // off the main thread these run rFunc there and return true, on it they return false + bool forwardToMainThread( const boost::function0<void>& rFunc ); + template< typename R > R callOnMainThread( const boost::function0<R>& rFunc ) + { + R aResult = R(); + mpSalYieldMutex->runOnMainThread( boost::bind( &AquaSalInstance::assignResult<R>, &aResult, boost::cref( rFunc ) ) ); + return aResult; + } + void startedPrintJob() { mnActivePrintJobs++; } void endedPrintJob() { mnActivePrintJobs--; }
