/************************************************************** * * 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 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. * *************************************************************/ // MARKER(update_precomp.py): autogen include statement, do not remove #include "precompiled_vcl.hxx" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define USE_SM_EXTENSION #if OSL_DEBUG_LEVEL > 1 #include static bool bFirstAssert = true; #endif #if OSL_DEBUG_LEVEL > 1 inline void SMprintf( const char* pFormat, ... ) #else inline void SMprintf( const char*, ... ) #endif { #if OSL_DEBUG_LEVEL > 1 FILE* fp = fopen( "/tmp/sessionlog.txt", bFirstAssert ? "w" : "a" ); if(!fp) return; bFirstAssert = false; std::va_list ap; va_start( ap, pFormat ); vfprintf( fp, pFormat, ap ); fclose( fp ); va_end( ap ); #endif }; static IceSalSession* pOneInstance = NULL; SalSession* X11SalInstance::CreateSalSession() { if( ! pOneInstance ) pOneInstance = new IceSalSession(); return pOneInstance; } /* * class IceSalSession */ static X11SalFrame* pOldStyleSaveFrame = NULL; IceSalSession::IceSalSession() { } IceSalSession::~IceSalSession() { if( pOneInstance == this ) pOneInstance = NULL; } void IceSalSession::queryInteraction() { if( ! SessionManagerClient::queryInteraction() ) { SalSessionInteractionEvent aEvent( false ); CallCallback( &aEvent ); } } void IceSalSession::interactionDone() { SessionManagerClient::interactionDone( false ); } void IceSalSession::saveDone() { SessionManagerClient::saveDone(); if( pOldStyleSaveFrame ) { // note: does nothing if not running in generic plugin X11SalFrame::SaveYourselfDone( pOldStyleSaveFrame ); } } bool IceSalSession::cancelShutdown() { SessionManagerClient::interactionDone( true ); return false; } void IceSalSession::handleOldX11SaveYourself( SalFrame* pFrame ) { // do this only once if( ! pOldStyleSaveFrame ) { pOldStyleSaveFrame = static_cast(pFrame); if( pOneInstance ) { SalSessionSaveRequestEvent aEvent( true, false ); pOneInstance->CallCallback( &aEvent ); } } } extern "C" void SAL_CALL ICEConnectionWorker( void* ); class ICEConnectionObserver { friend void SAL_CALL ICEConnectionWorker(void*); static sal_Bool bIsWatching; static void ICEWatchProc( IceConn connection, IcePointer client_data, Bool opening, IcePointer* watch_data ); static struct pollfd* pFilehandles; static IceConn* pConnections; static int nConnections; static int nWakeupFiles[2]; static oslMutex ICEMutex; static oslThread ICEThread; #ifdef USE_SM_EXTENSION static IceIOErrorHandler origIOErrorHandler; static IceErrorHandler origErrorHandler; #endif public: static void activate(); static void deactivate(); static void lock(); static void unlock(); static void wakeup(); }; SmcConn SessionManagerClient::aSmcConnection = NULL; ByteString SessionManagerClient::aClientID; sal_Bool ICEConnectionObserver::bIsWatching = sal_False; struct pollfd* ICEConnectionObserver::pFilehandles = NULL; IceConn* ICEConnectionObserver::pConnections = NULL; int ICEConnectionObserver::nConnections = 0; oslMutex ICEConnectionObserver::ICEMutex = NULL; oslThread ICEConnectionObserver::ICEThread = NULL; int ICEConnectionObserver::nWakeupFiles[2] = { 0, 0 }; #ifdef USE_SM_EXTENSION IceIOErrorHandler ICEConnectionObserver::origIOErrorHandler = NULL; IceErrorHandler ICEConnectionObserver::origErrorHandler = NULL; static void IgnoreIceErrors(IceConn, Bool, int, unsigned long, int, int, IcePointer) { } static void IgnoreIceIOErrors(IceConn) { } #endif // HACK bool SessionManagerClient::bDocSaveDone = false; static SmProp* pSmProps = NULL; static SmProp** ppSmProps = NULL; static int nSmProps = 0; static unsigned char *pSmRestartHint = NULL; static void BuildSmPropertyList() { if( ! pSmProps ) { ByteString aExec( SessionManagerClient::getExecName(), osl_getThreadTextEncoding() ); nSmProps = 5; pSmProps = new SmProp[ nSmProps ]; pSmProps[ 0 ].name = const_cast(SmCloneCommand); pSmProps[ 0 ].type = const_cast(SmLISTofARRAY8); pSmProps[ 0 ].num_vals = 1; pSmProps[ 0 ].vals = new SmPropValue; pSmProps[ 0 ].vals->length = aExec.Len()+1; pSmProps[ 0 ].vals->value = strdup( aExec.GetBuffer() ); pSmProps[ 1 ].name = const_cast(SmProgram); pSmProps[ 1 ].type = const_cast(SmARRAY8); pSmProps[ 1 ].num_vals = 1; pSmProps[ 1 ].vals = new SmPropValue; pSmProps[ 1 ].vals->length = aExec.Len()+1; pSmProps[ 1 ].vals->value = strdup( aExec.GetBuffer() ); pSmProps[ 2 ].name = const_cast(SmRestartCommand); pSmProps[ 2 ].type = const_cast(SmLISTofARRAY8); pSmProps[ 2 ].num_vals = 3; pSmProps[ 2 ].vals = new SmPropValue[3]; pSmProps[ 2 ].vals[0].length = aExec.Len()+1; pSmProps[ 2 ].vals[0].value = strdup( aExec.GetBuffer() ); ByteString aRestartOption( "-session=" ); aRestartOption.Append( SessionManagerClient::getSessionID() ); pSmProps[ 2 ].vals[1].length = aRestartOption.Len()+1; pSmProps[ 2 ].vals[1].value = strdup( aRestartOption.GetBuffer() ); ByteString aRestartOptionNoLogo( "-nologo" ); pSmProps[ 2 ].vals[2].length = aRestartOptionNoLogo.Len()+1; pSmProps[ 2 ].vals[2].value = strdup( aRestartOptionNoLogo.GetBuffer() ); rtl::OUString aUserName; rtl::OString aUser; oslSecurity aSec = osl_getCurrentSecurity(); if( aSec ) { osl_getUserName( aSec, &aUserName.pData ); aUser = rtl::OUStringToOString( aUserName, osl_getThreadTextEncoding() ); osl_freeSecurityHandle( aSec ); } pSmProps[ 3 ].name = const_cast(SmUserID); pSmProps[ 3 ].type = const_cast(SmARRAY8); pSmProps[ 3 ].num_vals = 1; pSmProps[ 3 ].vals = new SmPropValue; pSmProps[ 3 ].vals->value = strdup( aUser.getStr() ); pSmProps[ 3 ].vals->length = strlen( (char *)pSmProps[ 3 ].vals->value )+1; pSmProps[ 4 ].name = const_cast(SmRestartStyleHint); pSmProps[ 4 ].type = const_cast(SmCARD8); pSmProps[ 4 ].num_vals = 1; pSmProps[ 4 ].vals = new SmPropValue; pSmProps[ 4 ].vals->value = malloc(1); pSmRestartHint = (unsigned char *)pSmProps[ 4 ].vals->value; *pSmRestartHint = SmRestartIfRunning; pSmProps[ 4 ].vals->length = 1; ppSmProps = new SmProp*[ nSmProps ]; for( int i = 0; i < nSmProps; i++ ) ppSmProps[ i ] = &pSmProps[i]; } } bool SessionManagerClient::checkDocumentsSaved() { return bDocSaveDone; } IMPL_STATIC_LINK( SessionManagerClient, SaveYourselfHdl, void*, EMPTYARG ) { SMprintf( "posting save documents event shutdown = %s\n", (pThis!=0) ? "true" : "false" ); static bool bFirstShutdown=true; if (pThis != 0 && bFirstShutdown) //first shutdown request { bFirstShutdown = false; /* If we have no actual frames open, e.g. we launched a quickstarter, and then shutdown all our frames leaving just a quickstarter running, then we don't want to launch an empty toplevel frame on the next start. (The job of scheduling the restart of the quick-starter is a task of the quick-starter) */ *pSmRestartHint = SmRestartNever; const std::list< SalFrame* >& rFrames = GetX11SalData()->GetDisplay()->getFrames(); for( std::list< SalFrame* >::const_iterator it = rFrames.begin(); it != rFrames.end(); ++it ) { Window *pWindow = (*it)->GetWindow(); if (pWindow && pWindow->IsVisible()) { *pSmRestartHint = SmRestartIfRunning; break; } } } if( pOneInstance ) { SalSessionSaveRequestEvent aEvent( pThis != 0, false ); pOneInstance->CallCallback( &aEvent ); } else saveDone(); return 0; } IMPL_STATIC_LINK_NOINSTANCE( SessionManagerClient, InteractionHdl, void*, EMPTYARG ) { SMprintf( "interaction link\n" ); if( pOneInstance ) { SalSessionInteractionEvent aEvent( true ); pOneInstance->CallCallback( &aEvent ); } return 0; } IMPL_STATIC_LINK_NOINSTANCE( SessionManagerClient, ShutDownCancelHdl, void*, EMPTYARG ) { SMprintf( "shutdown cancel\n" ); if( pOneInstance ) { SalSessionShutdownCancelEvent aEvent; pOneInstance->CallCallback( &aEvent ); } return 0; } void SessionManagerClient::SaveYourselfProc( SmcConn, SmPointer, int save_type, Bool shutdown, int interact_style, Bool ) { SMprintf( "Session: save yourself, save_type = %s, shutdown = %s, interact_style = %s, fast = %s\n", save_type == SmSaveLocal ? "SmcSaveLocal" : ( save_type == SmSaveGlobal ? "SmcSaveGlobal" : ( save_type == SmSaveBoth ? "SmcSaveBoth" : "" ) ), shutdown ? "true" : "false", interact_style == SmInteractStyleNone ? "SmInteractStyleNone" : ( interact_style == SmInteractStyleErrors ? "SmInteractStyleErrors" : ( interact_style == SmInteractStyleAny ? "SmInteractStyleAny" : "" ) ), false ? "true" : "false" ); BuildSmPropertyList(); #ifdef USE_SM_EXTENSION bDocSaveDone = false; /* #i49875# some session managers send a "die" message if the * saveDone does not come early enough for their convenience * this can occasionally happen on startup, especially the first * startup. So shortcut the "not shutting down" case since the * upper layers are currently not interested in that event anyway. */ if( ! shutdown ) { SessionManagerClient::saveDone(); return; } Application::PostUserEvent( STATIC_LINK( (void*)(shutdown ? 0xffffffff : 0x0), SessionManagerClient, SaveYourselfHdl ) ); SMprintf( "waiting for save yourself event to be processed\n" ); #endif } IMPL_STATIC_LINK_NOINSTANCE( SessionManagerClient, ShutDownHdl, void*, EMPTYARG ) { if( pOneInstance ) { SalSessionQuitEvent aEvent; pOneInstance->CallCallback( &aEvent ); } const std::list< SalFrame* >& rFrames = GetX11SalData()->GetDisplay()->getFrames(); SMprintf( rFrames.begin() != rFrames.end() ? "shutdown on first frame\n" : "shutdown event but no frame\n" ); if( rFrames.begin() != rFrames.end() ) rFrames.front()->CallCallback( SALEVENT_SHUTDOWN, 0 ); return 0; } void SessionManagerClient::DieProc( SmcConn connection, SmPointer ) { SMprintf( "Session: die\n" ); if( connection == aSmcConnection ) { Application::PostUserEvent( STATIC_LINK( NULL, SessionManagerClient, ShutDownHdl ) ); SMprintf( "waiting for shutdown event to be processed\n" ); } } void SessionManagerClient::SaveCompleteProc( SmcConn, SmPointer ) { SMprintf( "Session: save complete\n" ); } void SessionManagerClient::ShutdownCanceledProc( SmcConn connection, SmPointer ) { SMprintf( "Session: shutdown canceled\n" ); if( connection == aSmcConnection ) Application::PostUserEvent( STATIC_LINK( NULL, SessionManagerClient, ShutDownCancelHdl ) ); } void SessionManagerClient::InteractProc( SmcConn connection, SmPointer ) { SMprintf( "Session: interaction request completed\n" ); if( connection == aSmcConnection ) Application::PostUserEvent( STATIC_LINK( NULL, SessionManagerClient, InteractionHdl ) ); } void SessionManagerClient::saveDone() { if( aSmcConnection ) { ICEConnectionObserver::lock(); SmcSetProperties( aSmcConnection, nSmProps, ppSmProps ); SmcSaveYourselfDone( aSmcConnection, True ); SMprintf( "sent SaveYourselfDone SmRestartHint of %d\n", *pSmRestartHint ); bDocSaveDone = true; ICEConnectionObserver::unlock(); } } void SessionManagerClient::open() { static SmcCallbacks aCallbacks; #ifdef USE_SM_EXTENSION // this is the way Xt does it, so we can too if( ! aSmcConnection && getenv( "SESSION_MANAGER" ) ) { char aErrBuf[1024]; ICEConnectionObserver::activate(); ICEConnectionObserver::lock(); char* pClientID = NULL; const ByteString& rPrevId( getPreviousSessionID() ); aCallbacks.save_yourself.callback = SaveYourselfProc; aCallbacks.save_yourself.client_data = NULL; aCallbacks.die.callback = DieProc; aCallbacks.die.client_data = NULL; aCallbacks.save_complete.callback = SaveCompleteProc; aCallbacks.save_complete.client_data = NULL; aCallbacks.shutdown_cancelled.callback = ShutdownCanceledProc; aCallbacks.shutdown_cancelled.client_data = NULL; aSmcConnection = SmcOpenConnection( NULL, NULL, SmProtoMajor, SmProtoMinor, SmcSaveYourselfProcMask | SmcDieProcMask | SmcSaveCompleteProcMask | SmcShutdownCancelledProcMask , &aCallbacks, rPrevId.Len() ? const_cast(rPrevId.GetBuffer()) : NULL, &pClientID, sizeof( aErrBuf ), aErrBuf ); if( ! aSmcConnection ) SMprintf( "SmcOpenConnection failed: %s\n", aErrBuf ); else SMprintf( "SmcOpenConnection succeeded, client ID is \"%s\"\n", pClientID ); aClientID = ByteString( pClientID ); free( pClientID ); pClientID = NULL; ICEConnectionObserver::unlock(); SalDisplay* pDisp = GetX11SalData()->GetDisplay(); if( pDisp->GetDrawable( pDisp->GetDefaultScreenNumber() ) && aClientID.Len() ) { XChangeProperty( pDisp->GetDisplay(), pDisp->GetDrawable( pDisp->GetDefaultScreenNumber() ), XInternAtom( pDisp->GetDisplay(), "SM_CLIENT_ID", False ), XA_STRING, 8, PropModeReplace, (unsigned char*)aClientID.GetBuffer(), aClientID.Len() ); } } else if( ! aSmcConnection ) SMprintf( "no SESSION_MANAGER\n" ); #endif } const ByteString& SessionManagerClient::getSessionID() { return aClientID; } void SessionManagerClient::close() { if( aSmcConnection ) { #ifdef USE_SM_EXTENSION ICEConnectionObserver::lock(); SMprintf( "attempting SmcCloseConnection\n" ); SmcCloseConnection( aSmcConnection, 0, NULL ); SMprintf( "SmcConnection closed\n" ); ICEConnectionObserver::unlock(); ICEConnectionObserver::deactivate(); #endif aSmcConnection = NULL; } } bool SessionManagerClient::queryInteraction() { bool bRet = false; if( aSmcConnection ) { ICEConnectionObserver::lock(); if( SmcInteractRequest( aSmcConnection, SmDialogNormal, InteractProc, NULL ) ) bRet = true; ICEConnectionObserver::unlock(); } return bRet; } void SessionManagerClient::interactionDone( bool bCancelShutdown ) { if( aSmcConnection ) { ICEConnectionObserver::lock(); SmcInteractDone( aSmcConnection, bCancelShutdown ? True : False ); ICEConnectionObserver::unlock(); } } String SessionManagerClient::getExecName() { rtl::OUString aExec, aSysExec; osl_getExecutableFile( &aExec.pData ); osl_getSystemPathFromFileURL( aExec.pData, &aSysExec.pData ); int nPos = aSysExec.indexOf( rtl::OUString::createFromAscii( ".bin" ) ); if( nPos != -1 ) aSysExec = aSysExec.copy( 0, nPos ); return aSysExec; } const ByteString& SessionManagerClient::getPreviousSessionID() { static ByteString aPrevId; int nCommands = osl_getCommandArgCount(); for( int i = 0; i < nCommands; i++ ) { ::rtl::OUString aArg; osl_getCommandArg( i, &aArg.pData ); if( aArg.compareToAscii( "-session=", 9 ) == 0 ) { aPrevId = ByteString( ::rtl::OUStringToOString( aArg.copy( 9 ), osl_getThreadTextEncoding() ) ); break; } } SMprintf( "previous ID = \"%s\"\n", aPrevId.GetBuffer() ); return aPrevId; } void ICEConnectionObserver::lock() { osl_acquireMutex( ICEMutex ); } void ICEConnectionObserver::unlock() { osl_releaseMutex( ICEMutex ); } void ICEConnectionObserver::activate() { if( ! bIsWatching ) { nWakeupFiles[0] = nWakeupFiles[1] = 0; ICEMutex = osl_createMutex(); bIsWatching = sal_True; #ifdef USE_SM_EXTENSION /* * Default handlers call exit, we don't care that strongly if something * happens to fail */ origIOErrorHandler = IceSetIOErrorHandler( IgnoreIceIOErrors ); origErrorHandler = IceSetErrorHandler( IgnoreIceErrors ); IceAddConnectionWatch( ICEWatchProc, NULL ); #endif } } void ICEConnectionObserver::deactivate() { if( bIsWatching ) { lock(); bIsWatching = sal_False; #ifdef USE_SM_EXTENSION IceRemoveConnectionWatch( ICEWatchProc, NULL ); IceSetErrorHandler( origErrorHandler ); IceSetIOErrorHandler( origIOErrorHandler ); #endif nConnections = 0; if( ICEThread ) { osl_terminateThread( ICEThread ); wakeup(); } unlock(); if( ICEThread ) { osl_joinWithThread( ICEThread ); osl_destroyThread( ICEThread ); close( nWakeupFiles[1] ); close( nWakeupFiles[0] ); ICEThread = NULL; } osl_destroyMutex( ICEMutex ); ICEMutex = NULL; } } void ICEConnectionObserver::wakeup() { char cChar = 'w'; write( nWakeupFiles[1], &cChar, 1 ); } void ICEConnectionWorker( void* ) { #ifdef USE_SM_EXTENSION while( osl_scheduleThread(ICEConnectionObserver::ICEThread) && ICEConnectionObserver::nConnections ) { ICEConnectionObserver::lock(); int nConnectionsBefore = ICEConnectionObserver::nConnections; int nBytes = sizeof( struct pollfd )*(nConnectionsBefore+1); struct pollfd* pLocalFD = (struct pollfd*)rtl_allocateMemory( nBytes ); rtl_copyMemory( pLocalFD, ICEConnectionObserver::pFilehandles, nBytes ); ICEConnectionObserver::unlock(); int nRet = poll( pLocalFD,nConnectionsBefore+1,-1 ); bool bWakeup = (pLocalFD[0].revents & POLLIN); rtl_freeMemory( pLocalFD ); if( nRet < 1 ) continue; // clear wakeup pipe if( bWakeup ) { char buf[4]; while( read( ICEConnectionObserver::nWakeupFiles[0], buf, sizeof( buf ) ) > 0 ) ; SMprintf( "file handles active in wakeup: %d\n", nRet ); if( nRet == 1 ) continue; } // check fd's after we obtained the lock ICEConnectionObserver::lock(); if( ICEConnectionObserver::nConnections > 0 && ICEConnectionObserver::nConnections == nConnectionsBefore ) { nRet = poll( ICEConnectionObserver::pFilehandles+1, ICEConnectionObserver::nConnections, 0 ); if( nRet > 0 ) { SMprintf( "IceProcessMessages\n" ); Bool bReply; for( int i = 0; i < ICEConnectionObserver::nConnections; i++ ) if( ICEConnectionObserver::pFilehandles[i+1].revents & POLLIN ) IceProcessMessages( ICEConnectionObserver::pConnections[i], NULL, &bReply ); } } ICEConnectionObserver::unlock(); } #endif SMprintf( "shutting donw ICE dispatch thread\n" ); } void ICEConnectionObserver::ICEWatchProc( IceConn connection, IcePointer, Bool opening, IcePointer* ) { // note: this is a callback function for ICE // this implicitly means that a call into ICE lib is calling this // so the ICEMutex MUST already be locked by the caller #ifdef USE_SM_EXTENSION if( opening ) { int fd = IceConnectionNumber( connection ); nConnections++; pConnections = (IceConn*)rtl_reallocateMemory( pConnections, sizeof( IceConn )*nConnections ); pFilehandles = (struct pollfd*)rtl_reallocateMemory( pFilehandles, sizeof( struct pollfd )*(nConnections+1) ); pConnections[ nConnections-1 ] = connection; pFilehandles[ nConnections ].fd = fd; pFilehandles[ nConnections ].events = POLLIN; if( nConnections == 1 ) { if( ! pipe( nWakeupFiles ) ) { int flags; pFilehandles[0].fd = nWakeupFiles[0]; pFilehandles[0].events = POLLIN; // set close-on-exec and nonblock descriptor flag. if ((flags = fcntl (nWakeupFiles[0], F_GETFD)) != -1) { flags |= FD_CLOEXEC; fcntl (nWakeupFiles[0], F_SETFD, flags); } if ((flags = fcntl (nWakeupFiles[0], F_GETFL)) != -1) { flags |= O_NONBLOCK; fcntl (nWakeupFiles[0], F_SETFL, flags); } // set close-on-exec and nonblock descriptor flag. if ((flags = fcntl (nWakeupFiles[1], F_GETFD)) != -1) { flags |= FD_CLOEXEC; fcntl (nWakeupFiles[1], F_SETFD, flags); } if ((flags = fcntl (nWakeupFiles[1], F_GETFL)) != -1) { flags |= O_NONBLOCK; fcntl (nWakeupFiles[1], F_SETFL, flags); } ICEThread = osl_createSuspendedThread( ICEConnectionWorker, NULL ); osl_resumeThread( ICEThread ); } } } else { for( int i = 0; i < nConnections; i++ ) { if( pConnections[i] == connection ) { if( i < nConnections-1 ) { rtl_moveMemory( pConnections+i, pConnections+i+1, sizeof( IceConn )*(nConnections-i-1) ); rtl_moveMemory( pFilehandles+i+1, pFilehandles+i+2, sizeof( struct pollfd )*(nConnections-i-1) ); } nConnections--; pConnections = (IceConn*)rtl_reallocateMemory( pConnections, sizeof( IceConn )*nConnections ); pFilehandles = (struct pollfd*)rtl_reallocateMemory( pFilehandles, sizeof( struct pollfd )*(nConnections+1) ); break; } } if( nConnections == 0 && ICEThread ) { SMprintf( "terminating ICEThread\n" ); osl_terminateThread( ICEThread ); wakeup(); // must release the mutex here osl_releaseMutex( ICEMutex ); osl_joinWithThread( ICEThread ); osl_destroyThread( ICEThread ); close( nWakeupFiles[1] ); close( nWakeupFiles[0] ); ICEThread = NULL; } } SMprintf( "ICE connection on %d %s\n", IceConnectionNumber( connection ), opening ? "inserted" : "removed" ); SMprintf( "Display connection is %d\n", ConnectionNumber( GetX11SalData()->GetDisplay()->GetDisplay() ) ); #endif }