From b052486e2ee1b2c53e1ff0f494eb8d5318f128b5 Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Sun, 20 May 2012 01:06:06 -0400 Subject: [PATCH] Update to newer kdsingleappguard --- src/TomahawkApp.cpp | 19 +- .../kdlockedsharedmemorypointer.cpp | 10 +- .../kdsingleapplicationguard.cpp | 1302 ++++++++++++----- .../kdsingleapplicationguard.h | 130 +- src/main.cpp | 2 +- 5 files changed, 1076 insertions(+), 387 deletions(-) diff --git a/src/TomahawkApp.cpp b/src/TomahawkApp.cpp index 33289da68..21dd156ad 100644 --- a/src/TomahawkApp.cpp +++ b/src/TomahawkApp.cpp @@ -680,28 +680,29 @@ TomahawkApp::loadUrl( const QString& url ) void TomahawkApp::instanceStarted( KDSingleApplicationGuard::Instance instance ) { - tDebug( LOGINFO ) << "Instance started!" << instance.pid << instance.arguments; + tDebug( LOGINFO ) << "Instance started!" << instance.pid() << instance.arguments(); + const QStringList arguments = instance.arguments(); - if ( instance.arguments.size() < 2 ) + if ( arguments.size() < 2 ) return; - QString arg1 = instance.arguments[ 1 ]; + QString arg1 = arguments[ 1 ]; if ( loadUrl( arg1 ) ) { activate(); return; } - if ( instance.arguments.contains( "--next" ) ) + if ( arguments.contains( "--next" ) ) AudioEngine::instance()->next(); - else if ( instance.arguments.contains( "--prev" ) ) + else if ( arguments.contains( "--prev" ) ) AudioEngine::instance()->previous(); - else if ( instance.arguments.contains( "--playpause" ) ) + else if ( arguments.contains( "--playpause" ) ) AudioEngine::instance()->playPause(); - else if ( instance.arguments.contains( "--play" ) ) + else if ( arguments.contains( "--play" ) ) AudioEngine::instance()->play(); - else if ( instance.arguments.contains( "--pause" ) ) + else if ( arguments.contains( "--pause" ) ) AudioEngine::instance()->pause(); - else if ( instance.arguments.contains( "--stop" ) ) + else if ( arguments.contains( "--stop" ) ) AudioEngine::instance()->stop(); } diff --git a/src/libtomahawk/thirdparty/kdsingleapplicationguard/kdlockedsharedmemorypointer.cpp b/src/libtomahawk/thirdparty/kdsingleapplicationguard/kdlockedsharedmemorypointer.cpp index e1fe10a4d..56ab8ebe4 100644 --- a/src/libtomahawk/thirdparty/kdsingleapplicationguard/kdlockedsharedmemorypointer.cpp +++ b/src/libtomahawk/thirdparty/kdsingleapplicationguard/kdlockedsharedmemorypointer.cpp @@ -31,9 +31,9 @@ void * KDLockedSharedMemoryPointerBase::get() { const void * KDLockedSharedMemoryPointerBase::get() const { return mem ? mem->data() : 0 ; } - + size_t KDLockedSharedMemoryPointerBase::byteSize() const { - return mem->size(); + return mem ? mem->size() : 0; } /*! @@ -137,7 +137,7 @@ size_t KDLockedSharedMemoryPointerBase::byteSize() const { (The exception safety of this class has not been evaluated yet.) - KDLockedSharedMemoryArray is a smart immutable pointer, which gives convenient and safe access to array data stored in a QSharedMemory + KDLockedSharedMemoryArray is a smart immutable pointer, which gives convenient and safe access to array data stored in a QSharedMemory data segment. The content of a KDLockedSharedMemoryArray cannot be changed during it's lifetime. @@ -248,7 +248,7 @@ size_t KDLockedSharedMemoryPointerBase::byteSize() const { /*! \fn KDLockedSharedMemoryArray::size_type KDLockedSharedMemoryArray::size() const - Returns the size of this array. The size is calculated from the storage size of T and + Returns the size of this array. The size is calculated from the storage size of T and the size of the shared memory segment. \since_f 2.2 */ @@ -461,7 +461,7 @@ KDAB_UNITTEST_SIMPLE( KDLockedSharedMemoryPointer, "kdcoretools" ) { assertEqual( a[ i ].n, i ); assertEqual( a.front().n, 0u ); assertEqual( a.back().n, a.size() - 1 ); - + std::copy( v.begin(), v.end(), a.rbegin() ); for( uint i = 0; i < a.size(); ++i ) assertEqual( a[ i ].n, a.size() - 1 - i ); diff --git a/src/libtomahawk/thirdparty/kdsingleapplicationguard/kdsingleapplicationguard.cpp b/src/libtomahawk/thirdparty/kdsingleapplicationguard/kdsingleapplicationguard.cpp index b7c04c2d6..b1a13ac8c 100644 --- a/src/libtomahawk/thirdparty/kdsingleapplicationguard/kdsingleapplicationguard.cpp +++ b/src/libtomahawk/thirdparty/kdsingleapplicationguard/kdsingleapplicationguard.cpp @@ -1,93 +1,301 @@ #include "kdsingleapplicationguard.h" +#if QT_VERSION >= 0x040400 || defined(DOXYGEN_RUN) +#ifndef QT_NO_SHAREDMEMORY + +#include "kdsharedmemorylocker.h" +#include "kdlockedsharedmemorypointer.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#ifndef Q_WS_WIN +#include +#include +#endif + +#ifdef Q_WS_WIN +#include +typedef signed int ssize_t; +#endif + +using namespace kdtools; + +#ifndef KDSINGLEAPPLICATIONGUARD_TIMEOUT_SECONDS +#define KDSINGLEAPPLICATIONGUARD_TIMEOUT_SECONDS 10 +#endif + #ifndef KDSINGLEAPPLICATIONGUARD_NUMBER_OF_PROCESSES #define KDSINGLEAPPLICATIONGUARD_NUMBER_OF_PROCESSES 10 #endif #ifndef KDSINGLEAPPLICATIONGUARD_MAX_COMMAND_LINE -#define KDSINGLEAPPLICATIONGUARD_MAX_COMMAND_LINE 1024 +#define KDSINGLEAPPLICATIONGUARD_MAX_COMMAND_LINE 32768 #endif +static unsigned int KDSINGLEAPPLICATIONGUARD_SHM_VERSION = 0; +Q_GLOBAL_STATIC_WITH_ARGS( int, registerInstanceType, + (qRegisterMetaType()) ) -KDSingleApplicationGuard::Instance::Instance( const QStringList& args, qint64 p ) -: arguments( args ), -pid( p ) -{ -} +/*! + \class KDSingleApplicationGuard::Instance + \relates KDSingleApplicationGuard + \ingroup core + \brief Information about instances a KDSingleApplicationGuard knows about -#if QT_VERSION < 0x040400 + Instance represents instances of applications under + KDSingleApplicationGuard protection, and allows access to their + pid() and the arguments() they were started with. +*/ -class KDSingleApplicationGuard::Private -{ +class KDSingleApplicationGuard::Instance::Private : public QSharedData { + friend class ::KDSingleApplicationGuard::Instance; +public: + Private( const QStringList & args, bool truncated, qint64 pid ) + : pid( pid ), arguments( args ), truncated( truncated ) {} + +private: + qint64 pid; + QStringList arguments; + bool truncated; }; -KDSingleApplicationGuard::KDSingleApplicationGuard( QCoreApplication*, Policy ) -{ - qWarning( "KD Tools was compiled with a Qt version prior to 4.4. SingleApplicationGuard won't work." ); -} - -KDSingleApplicationGuard::~KDSingleApplicationGuard() -{ -} - -void KDSingleApplicationGuard::shutdownOtherInstances() -{ -} - -void KDSingleApplicationGuard::killOtherInstances() -{ -} - -void KDSingleApplicationGuard::timerEvent( QTimerEvent* ) -{ -} -#else - -#include -#include - -#include "kdsharedmemorylocker.h" -#include "kdlockedsharedmemorypointer.h" - -#include -#include -#include - -#ifndef Q_WS_WIN -#include -#endif - -using namespace kdtools; +struct ProcessInfo; /*! - * \class KDSingleApplicationGuard KDSingleApplicationGuard - * \brief A guard to protect an application from having several instances. - * - * KDSingleApplicationGuard can be used to make sure only one instance of an - * application is running at the same time. - * - * \note As KDSingleApplicationGuard uses QSharedMemory Qt 4.4 or later is required + \internal + */ +class KDSingleApplicationGuard::Private +{ + friend class ::KDSingleApplicationGuard; + friend class ::KDSingleApplicationGuard::Instance; + friend struct ::ProcessInfo; + KDSingleApplicationGuard * const q; +public: + Private( Policy policy, KDSingleApplicationGuard* qq ); + ~Private(); + + void create( const QStringList& arguments ); + + bool checkOperational( const char * function, const char * act ) const; + bool checkOperationalPrimary( const char * function, const char * act ) const; + + struct segmentheader + { + size_t size : 16; + }; + + static void sharedmem_free( char* ); + static char* sharedmem_malloc( size_t size ); + +private: + void shutdownInstance(); + void poll(); + +private: + static KDSingleApplicationGuard* primaryInstance; + +private: + QBasicTimer timer; + QSharedMemory mem; + int id; + Policy policy; + bool operational; + bool exitRequested; +}; + +/*! + \internal +*/ +KDSingleApplicationGuard::Instance::Instance( const QStringList & args, bool truncated, qint64 p ) + : d( new Private( args, truncated, p ) ) +{ + d->ref.ref(); + (void)registerInstanceType(); +} + +/*! + Default constructor. Constructs in Instance that is \link isNull() + null\endlink. + + \sa isNull() +*/ +KDSingleApplicationGuard::Instance::Instance() : d( 0 ) {} + +/*! + Copy constructor. +*/ +KDSingleApplicationGuard::Instance::Instance( const Instance & other ) + : d( other.d ) +{ + if ( d ) + d->ref.ref(); +} + +/*! + Destructor. +*/ +KDSingleApplicationGuard::Instance::~Instance() +{ + if ( d && !d->ref.deref() ) + delete d; +} + +/*! + \fn KDSingleApplicationGuard::Instance::swap( Instance & other ) + + Swaps the contents of this and \a other. + + This function never throws exceptions. +*/ + +/*! + \fn KDSingleApplicationGuard::Instance::operator=( Instance other ) + + Assigns the contents of \a other to this. + + This function is strongly exception-safe. +*/ + +/*! + \fn std::swap( KDSingleApplicationGuard::Instance & lhs, KDSingleApplicationGuard::Instance & rhs ) + \relates KDSingleApplicationGuard::Instance + + Specialisation of std::swap() for + KDSingleApplicationGuard::Instance. Calls swap(). +*/ + +/*! + \fn qSwap( KDSingleApplicationGuard::Instance & lhs, KDSingleApplicationGuard::Instance & rhs ) + \relates KDSingleApplicationGuard::Instance + + Specialisation of qSwap() for + KDSingleApplicationGuard::Instance. Calls swap(). +*/ + +/*! + \fn KDSingleApplicationGuard::Instance::isNull() const + + Returns whether this instance is null. +*/ + +/*! + Returns whether this instance is valid. A valid instance is neither + null, nor does it have a negative PID. +*/ +bool KDSingleApplicationGuard::Instance::isValid() const +{ + return d && d->pid >= 0 ; +} + +/*! + Returns whether the #arguments are complete (\c false) or not (\c + true), e.g. because they have been truncated due to limited storage + space. + + \sa arguments() +*/ +bool KDSingleApplicationGuard::Instance::areArgumentsTruncated() const +{ + return d && d->truncated; +} + +/*! + Returns the arguments that this instance was started with. + + \sa areArgumentsTruncated() +*/ +const QStringList & KDSingleApplicationGuard::Instance::arguments() const +{ + if ( d ) + return d->arguments; + static const QStringList empty; + return empty; +} + +/*! + Returns the process-id (PID) of this instance. +*/ +qint64 KDSingleApplicationGuard::Instance::pid() const +{ + if ( d ) + return d->pid; + else + return -1; +} + +/*! + \class KDSingleApplicationGuard KDSingleApplicationGuard + \ingroup core + \brief A guard to protect an application from having several instances. + + KDSingleApplicationGuard can be used to make sure only one instance of an + application is running at the same time. + + \note As KDSingleApplicationGuard currently uses QSharedMemory, Qt + 4.4 or later is required. */ /*! - * \fn void KDSingleApplicationGuard::instanceStarted() - * This signal is emitted by the primary instance when ever one other - * instance was started. + \fn void KDSingleApplicationGuard::instanceStarted(const KDSingleApplicationGuard::Instance & instance) + + This signal is emitted by the primary instance whenever another + instance \a instance started. */ /*! - * \fn void KDSingleApplicationGuard::instanceExited() - * This signal is emitted by the primary instance when ever one other - * instance was exited. + \fn void KDSingleApplicationGuard::instanceExited(const KDSingleApplicationGuard::Instance & instance) + + This signal is emitted by the primary instance whenever another + instance \a instance exited. */ /*! - * \fn void KDSingleApplicationGuard::becamePrimaryInstance() - * This signal is emitted, when the current running application gets the new - * primary application. The old primary application has quit. + \fn void KDSingleApplicationGuard::raiseRequested() + + This signal is emitted when the current running application is requested + to raise its main window. +*/ + +/*! + \fn void KDSingleApplicationGuard::exitRequested() + + This signal is emitted when the current running application has been asked to exit + by calling kill on the instance. +*/ + +/*! + \fn void KDSingleApplicationGuard::becamePrimaryInstance() + + This signal is emitted when the current running application becomes + the new primary application. The old primary application has quit. */ +/*! + \fn void KDSingleApplicationGuard::becameSecondaryInstance() + + This signal is emmited when the primary instance became secondary instance. + This happens when the instance doesn't update its status for some (default 10) seconds. Another instance + got primary instance in that case. + */ + +/*! + \fn void KDSingleApplicationGuard::policyChanged( KDSingleApplicationGuard::Policy policy ) + + This signal is emitted when the #policy of the system changes. +*/ + enum Command { NoCommand = 0x00, @@ -96,259 +304,530 @@ enum Command FreeInstance = 0x04, ShutDownCommand = 0x08, KillCommand = 0x10, - BecomePrimaryCommand = 0x20 + BecomePrimaryCommand = 0x20, + RaiseCommand = 0x40 }; -Q_DECLARE_FLAGS( Commands, Command ) -Q_DECLARE_OPERATORS_FOR_FLAGS( Commands ) +static const quint16 PrematureEndOfOptions = -1; +static const quint16 RegularEndOfOptions = -2; struct ProcessInfo { + static const size_t MarkerSize = sizeof(quint16); + explicit ProcessInfo( Command c = FreeInstance, const QStringList& arguments = QStringList(), qint64 p = -1 ) - : command( c ), - pid( p ) + : pid( p ), + command( c ), + timestamp( 0 ), + commandline( 0 ) { - std::fill_n( commandline, KDSINGLEAPPLICATIONGUARD_MAX_COMMAND_LINE, '\0' ); - - int argpos = 0; - for( QStringList::const_iterator it = arguments.begin(); it != arguments.end(); ++it ) - { - const QByteArray arg = it->toLatin1(); - const int count = qMin( KDSINGLEAPPLICATIONGUARD_MAX_COMMAND_LINE - argpos, arg.count() ); - std::copy( arg.begin(), arg.begin() + count, commandline + argpos ); - argpos += arg.count() + 1; // makes sure there's a \0 between every parameter - } + setArguments( arguments ); } - QStringList arguments() const - { - QStringList result; + void setArguments( const QStringList & arguments ); + QStringList arguments( bool * prematureEnd ) const; - QByteArray arg; - for( int i = 0; i < KDSINGLEAPPLICATIONGUARD_MAX_COMMAND_LINE; ++i ) - { - if( commandline[ i ] == '\0' && !arg.isEmpty() ) - { - result.push_back( QString::fromLatin1( arg ) ); - arg.clear(); - } - else if( !commandline[ i ] == '\0' ) - { - arg.push_back( commandline[ i ] ); - } - } - - return result; - } - - Commands command; - char commandline[ KDSINGLEAPPLICATIONGUARD_MAX_COMMAND_LINE ]; qint64 pid; + quint32 command; + quint32 timestamp; + char* commandline; }; -bool operator==( const ProcessInfo& lhs, const ProcessInfo& rhs ) +static inline bool operator==( const ProcessInfo & lhs, const ProcessInfo & rhs ) { return lhs.command == rhs.command && - ::memcmp( lhs.commandline, rhs.commandline, KDSINGLEAPPLICATIONGUARD_MAX_COMMAND_LINE ) == 0; + ( lhs.commandline == rhs.commandline || ( lhs.commandline != 0 && rhs.commandline != 0 && ::strcmp( lhs.commandline, rhs.commandline ) == 0 ) ); } -bool operator!=( const ProcessInfo& lhs, const ProcessInfo& rhs ) +static inline bool operator!=( const ProcessInfo & lhs, const ProcessInfo & rhs ) { return !operator==( lhs, rhs ); } /*! - * This struct contains information about the managed process system. - * \internal + This struct contains information about the managed process system. + \internal */ struct InstanceRegister { - InstanceRegister( KDSingleApplicationGuard::Policy policy = KDSingleApplicationGuard::NoPolicy ) - : policy( policy ) + explicit InstanceRegister( KDSingleApplicationGuard::Policy policy = KDSingleApplicationGuard::NoPolicy ) + : policy( policy ), + maxInstances( KDSINGLEAPPLICATIONGUARD_NUMBER_OF_PROCESSES ), + version( 0 ) { - std::fill_n( info, KDSINGLEAPPLICATIONGUARD_NUMBER_OF_PROCESSES, ProcessInfo() ); + std::fill_n( commandLines, KDSINGLEAPPLICATIONGUARD_MAX_COMMAND_LINE, 0 ); ::memcpy( magicCookie, "kdsingleapp", 12 ); } /*! - * Returns wheter this register was properly initialized by the first instance. - */ + Returns whether this register was properly initialized by the first instance. + */ bool isValid() const { return ::strcmp( magicCookie, "kdsingleapp" ) == 0; } - ProcessInfo info[ KDSINGLEAPPLICATIONGUARD_NUMBER_OF_PROCESSES ]; - KDSingleApplicationGuard::Policy policy; char magicCookie[ 12 ]; + unsigned int policy : 8; + quint32 maxInstances : 20; + unsigned int version : 4; + ProcessInfo info[ KDSINGLEAPPLICATIONGUARD_NUMBER_OF_PROCESSES ]; + + char commandLines[ KDSINGLEAPPLICATIONGUARD_MAX_COMMAND_LINE ]; + + Q_DISABLE_COPY( InstanceRegister ) }; -bool operator==( const InstanceRegister& lhs, const InstanceRegister& rhs ) +void ProcessInfo::setArguments( const QStringList & arguments ) { - if( lhs.policy != rhs.policy ) - return false; + if( commandline != 0 ) + KDSingleApplicationGuard::Private::sharedmem_free( commandline ); - for( int i = 0; i < KDSINGLEAPPLICATIONGUARD_NUMBER_OF_PROCESSES; ++i ) - if( lhs.info[ i ] != rhs.info[ i ] ) - return false; + commandline = 0; + if( arguments.isEmpty() ) + return; - return true; -} - -/*! - * \internal - */ -class KDSingleApplicationGuard::Private -{ -public: - Private( KDSingleApplicationGuard* qq ) - : q( qq ), - id( -1 ) + size_t totalsize = MarkerSize; + Q_FOREACH( const QString& arg, arguments ) { - if( primaryInstance == 0 ) - primaryInstance = q; + const QByteArray utf8 = arg.toUtf8(); + totalsize += utf8.size() + MarkerSize; + } + InstanceRegister* const reg = reinterpret_cast( KDSingleApplicationGuard::Private::primaryInstance->d->mem.data() ); + this->commandline = KDSingleApplicationGuard::Private::sharedmem_malloc( totalsize ); + if( this->commandline == 0 ) + { + qWarning("KDSingleApplicationguard: out of memory when trying to save arguments.\n"); + return; } - ~Private() - { - if( primaryInstance == q ) - primaryInstance = 0; - } + char* const commandline = this->commandline + reinterpret_cast(reg->commandLines); - void shutdownInstance() + int argpos = 0; + Q_FOREACH( const QString & arg, arguments ) { - KDLockedSharedMemoryPointer< InstanceRegister > instances( &q->d->mem ); - instances->info[ q->d->id ].command = ExitedInstance; - - if( q->isPrimaryInstance() ) - { - // ohh... we need a new primary instance... - for( int i = 1; i < KDSINGLEAPPLICATIONGUARD_NUMBER_OF_PROCESSES; ++i ) - { - if( ( instances->info[ i ].command & ( FreeInstance | ExitedInstance | ShutDownCommand | KillCommand ) ) == 0 ) - { - instances->info[ i ].command |= BecomePrimaryCommand; - return; - } - } - // none found? then my species is dead :-( + const QByteArray utf8 = arg.toUtf8(); + const int required = MarkerSize + utf8.size() + MarkerSize ; + const int available = KDSINGLEAPPLICATIONGUARD_MAX_COMMAND_LINE - argpos ; + if ( required > available || utf8.size() > std::numeric_limits::max() ) { + // write a premature-eoo marker, and quit + memcpy( commandline + argpos, &PrematureEndOfOptions, MarkerSize ); + argpos += MarkerSize; + qWarning( "KDSingleApplicationGuard: argument list is too long (bytes required: %d, used: %d, available: %d", + required, argpos - 2, available ); + return; + } else { + const quint16 len16 = utf8.size(); + // write the size of the data... + memcpy( commandline + argpos, &len16, MarkerSize ); + argpos += MarkerSize; + // then the data + memcpy( commandline + argpos, utf8.data(), len16 ); + argpos += len16; } } + const ssize_t available = KDSINGLEAPPLICATIONGUARD_MAX_COMMAND_LINE - argpos; + assert( available >= static_cast( MarkerSize ) ); + memcpy( commandline + argpos, &RegularEndOfOptions, MarkerSize ); + argpos += MarkerSize; +} - static KDSingleApplicationGuard* primaryInstance; +QStringList ProcessInfo::arguments( bool * prematureEnd ) const +{ + QStringList result; + if( commandline == 0 ) + { + if( prematureEnd ) + *prematureEnd = true; + return result; + } -private: - KDSingleApplicationGuard* const q; + InstanceRegister* const reg = reinterpret_cast( KDSingleApplicationGuard::Private::primaryInstance->d->mem.data() ); + const char* const commandline = this->commandline + reinterpret_cast(reg->commandLines); -public: - Policy policy; - QSharedMemory mem; - int id; + int argpos = 0; + while ( true ) { + const int available = KDSINGLEAPPLICATIONGUARD_MAX_COMMAND_LINE - argpos ; + assert( available >= 2 ); + + quint16 marker; + memcpy( &marker, commandline + argpos, MarkerSize ); + argpos += MarkerSize; + + if ( marker == PrematureEndOfOptions ) { + if ( prematureEnd ) *prematureEnd = true; + break; + } + if ( marker == RegularEndOfOptions ) { + if ( prematureEnd ) *prematureEnd = false; + break; + } + + const int requested = MarkerSize + marker + MarkerSize ; + if ( requested > available ) { + const long long int p = pid; + qWarning( "KDSingleApplicationGuard: inconsistency detected when parsing command-line argument for process %lld", p ); + if ( prematureEnd ) *prematureEnd = true; + break; + } + + result.push_back( QString::fromUtf8( commandline + argpos, marker ) ); + argpos += marker; + } + + return result; +} + +KDSingleApplicationGuard::Private::~Private() +{ + if( primaryInstance == q ) + primaryInstance = 0; +} + +bool KDSingleApplicationGuard::Private::checkOperational( const char * function, const char * act ) const +{ + assert( function ); + assert( act ); + if ( !operational ) + qWarning( "KDSingleApplicationGuard::%s: need to be operational to %s", function, act ); + return operational; +} + +bool KDSingleApplicationGuard::Private::checkOperationalPrimary( const char * function, const char * act ) const +{ + if ( !checkOperational( function, act ) ) + return false; + if ( id != 0 ) + qWarning( "KDSingleApplicationGuard::%s: need to be primary to %s", function, act ); + return id == 0; +} + +struct segmentheader +{ + size_t size : 16; }; +void KDSingleApplicationGuard::Private::sharedmem_free( char* pointer ) +{ + InstanceRegister* const reg = reinterpret_cast( KDSingleApplicationGuard::Private::primaryInstance->d->mem.data() ); + char* const heap = reg->commandLines; + char* const heap_ptr = heap + reinterpret_cast(pointer) - sizeof( segmentheader ); + const segmentheader* const header = reinterpret_cast< const segmentheader* >( heap_ptr ); + const size_t size = header->size; + + char* end = heap + KDSINGLEAPPLICATIONGUARD_MAX_COMMAND_LINE; + + std::copy( heap_ptr + size, end, heap_ptr ); + std::fill( end - size, end, 0 ); + + for( uint i = 0; i < reg->maxInstances; ++i ) + { + if( reg->info[ i ].commandline > pointer ) + reg->info[ i ].commandline -= size + sizeof( segmentheader ); + } +} + +char* KDSingleApplicationGuard::Private::sharedmem_malloc( size_t size ) +{ + InstanceRegister* const reg = reinterpret_cast( KDSingleApplicationGuard::Private::primaryInstance->d->mem.data() ); + char* heap = reg->commandLines; + + while( heap + sizeof( segmentheader ) + size < reg->commandLines + KDSINGLEAPPLICATIONGUARD_MAX_COMMAND_LINE ) + { + segmentheader* const header = reinterpret_cast< segmentheader* >( heap ); + if( header->size == 0 ) + { + header->size = size; + return heap + sizeof( segmentheader ) - reinterpret_cast(reg->commandLines); + } + heap += sizeof( header ) + header->size; + } + return 0; +} + +void KDSingleApplicationGuard::Private::shutdownInstance() +{ + KDLockedSharedMemoryPointer< InstanceRegister > instances( &q->d->mem ); + instances->info[ q->d->id ].command |= ExitedInstance; + + if( q->isPrimaryInstance() ) + { + // ohh... we need a new primary instance... + for ( int i = 1, end = instances->maxInstances ; i < end ; ++i ) + { + if( ( instances->info[ i ].command & ( FreeInstance | ExitedInstance | ShutDownCommand | KillCommand ) ) == 0 ) + { + instances->info[ i ].command |= BecomePrimaryCommand; + return; + } + } + // none found? then my species is dead :-( + } +} + KDSingleApplicationGuard* KDSingleApplicationGuard::Private::primaryInstance = 0; -#ifndef Q_WS_WIN -void SIGINT_handler( int sig ) +/*! + Requests that the instance kills itself (by emitting exitRequested). + + If the instance has since exited, does nothing. + + \sa shutdown(), raise() +*/ +void KDSingleApplicationGuard::Instance::kill() { - if( sig == SIGINT && KDSingleApplicationGuard::Private::primaryInstance != 0 ) - KDSingleApplicationGuard::Private::primaryInstance->d->shutdownInstance(); + KDLockedSharedMemoryPointer< InstanceRegister > instances( &KDSingleApplicationGuard::Private::primaryInstance->d->mem ); + for ( int i = 0, end = instances->maxInstances ; i < end ; ++i ) + { + if( instances->info[ i ].pid != d->pid ) + continue; + if( ( instances->info[ i ].command & ( FreeInstance | ExitedInstance ) ) == 0 ) + instances->info[ i ].command = KillCommand; + } +} + +/*! + Requests that the instance shuts itself down (by calling QCoreApplication::quit()). + + If the instance has since exited, does nothing. + + \sa kill(), raise() +*/ +void KDSingleApplicationGuard::Instance::shutdown() +{ + KDLockedSharedMemoryPointer< InstanceRegister > instances( &KDSingleApplicationGuard::Private::primaryInstance->d->mem ); + for ( int i = 0, end = instances->maxInstances ; i < end ; ++i ) + { + if( instances->info[ i ].pid != d->pid ) + continue; + if( ( instances->info[ i ].command & ( FreeInstance | ExitedInstance ) ) == 0 ) + instances->info[ i ].command = ShutDownCommand; + } +} + +/*! + + Requests that the instance raises its main window. + + The effects are implementation-defined: the KDSingleApplicationGuard + corresponding to the instance will emit its \link + KDSingleApplicationGuard::raiseRequested() raiseRequested()\endlink + signal. + + If the instance has since exited, does nothing. + + \sa kill(), shutdown() +*/ +void KDSingleApplicationGuard::Instance::raise() +{ + KDLockedSharedMemoryPointer< InstanceRegister > instances( &KDSingleApplicationGuard::Private::primaryInstance->d->mem ); + for ( int i = 0, end = instances->maxInstances ; i < end ; ++i ) + { + if( instances->info[ i ].pid != d->pid ) + continue; + if( ( instances->info[ i ].command & ( FreeInstance | ExitedInstance ) ) == 0 ) + instances->info[ i ].command = RaiseCommand; + } +} + + +#ifndef Q_WS_WIN +// static +void KDSingleApplicationGuard::SIGINT_handler( int sig ) +{ + if( sig == SIGINT && Private::primaryInstance != 0 ) + Private::primaryInstance->d->shutdownInstance(); ::exit( 1 ); } #endif /*! - * Creates a new KDSingleApplicationGuard guarding \a parent from mulitply instances. - * If \a policy is AutoKillOtherInstances (the default), all instances, which try to start, - * are killed automatically and instanceStarted() is emitted. - * If \a policy is NoPolicy, the other instance will run and instanceStarted() is emitted. - */ -KDSingleApplicationGuard::KDSingleApplicationGuard( QCoreApplication* parent, Policy policy ) -: QObject( parent ), -d( new Private( this ) ) + \enum KDSingleApplicationGuard::Policy + + Defines the policy that a KDSingleApplicationGuard can enforce: +*/ + +/*! + \var KDSingleApplicationGuard::NoPolicy + + instanceStarted() is emitted, and the new instance allowed to continue. +*/ + +/*! + \var KDSingleApplicationGuard::AutoKillOtherInstances + + instanceStarted() is emitted, and the new instance is killed (Instance::kill()). +*/ + +/*! + Creates a new KDSingleApplicationGuard with arguments + QCoreApplication::arguments() and policy AutoKillOtherInstances, + passing \a parent to the base class constructor, as usual. +*/ +KDSingleApplicationGuard::KDSingleApplicationGuard( QObject * parent ) + : QObject( parent ), d( new Private( AutoKillOtherInstances, this ) ) { - const QString name = parent->applicationName(); - Q_ASSERT_X( !name.isEmpty(), "KDSingleApplicationGuard::KDSingleApplicationGuard", "applicationName must not be emty" ); - d->mem.setKey( name ); - - // if another instance crashed, the shared memory segment is still there on Unix - // the following lines trigger deletion in that case - #ifndef Q_WS_WIN - d->mem.attach(); - d->mem.detach(); - #endif - - d->policy = policy; - - const bool created = d->mem.create( sizeof( InstanceRegister ) ); - if( !created ) - { - if( !d->mem.attach() ) - { - qWarning( "KDSingleApplicationGuard: Could neither create nor attach to shared memory segment." ); - return; - } - - // lets wait till the other instance initialized the register - bool initialized = false; - while( !initialized ) - { - const KDLockedSharedMemoryPointer< InstanceRegister > instances( &d->mem ); - initialized = instances->isValid(); - } - } - - bool killMyself = false; - { - KDLockedSharedMemoryPointer< InstanceRegister > instances( &d->mem ); - - if( !created ) - { - // we're _not_ the first instance - // but the - bool killOurSelf = false; - - // find a new slot... - d->id = std::find( instances->info, instances->info + KDSINGLEAPPLICATIONGUARD_NUMBER_OF_PROCESSES, ProcessInfo() ) - instances->info; - ProcessInfo& info = instances->info[ d->id ]; - info = ProcessInfo( NewInstance, parent->arguments(), QCoreApplication::applicationPid() ); - killOurSelf = instances->policy == AutoKillOtherInstances; - d->policy = instances->policy; - - // but the signal that we tried to start was sent to the primary application - if( killOurSelf ) - { - info.command |= ExitedInstance; - killMyself = true; - } - } - else - { - // ok.... we are the first instance - InstanceRegister reg( policy ); // create a new list - d->id = 0; // our id = 0 - // and we've no command - reg.info[ 0 ] = ProcessInfo( NoCommand, parent->arguments(), QCoreApplication::applicationPid() ); - *instances = reg; // push this is the process list into shared memory - } - } - // call exit after we let the locker release our memory, as exit() is not guaranteed to clean up objects on the stack - if ( killMyself ) - exit( 1 ); - - #ifndef Q_WS_WIN - ::signal( SIGINT, SIGINT_handler ); - #endif - - // now listen for commands - startTimer( 750 ); + d->create( QCoreApplication::arguments() ); } /*! - * Destroys this SingleApplicationGuard. - * If this instance has been the primary instance and no other instance is existing anymore, - * the application is shut down completely. Otherwise the destructor selects another instance to - * be the primary instances. + Creates a new KDSingleApplicationGuard with arguments + QCoreApplication::arguments() and policy \a policy, passing \a + parent to the base class constructor, as usual. +*/ +KDSingleApplicationGuard::KDSingleApplicationGuard( Policy policy, QObject * parent ) + : QObject( parent ), d( new Private( policy, this ) ) +{ + d->create( QCoreApplication::arguments() ); +} + +/*! + Creates a new KDSingleApplicationGuard with arguments \a arguments + and policy AutoKillOtherInstances, passing \a parent to the base + class constructor, as usual. +*/ +KDSingleApplicationGuard::KDSingleApplicationGuard( const QStringList & arguments, QObject * parent ) + : QObject( parent ), d( new Private( AutoKillOtherInstances, this ) ) +{ + d->create( arguments ); +} + +/*! + Creates a new KDSingleApplicationGuard with arguments \a arguments + and policy \a policy, passing \a parent to the base class + constructor, as usual. +*/ +KDSingleApplicationGuard::KDSingleApplicationGuard( const QStringList & arguments, Policy policy, QObject * parent ) + : QObject( parent ), d( new Private( policy, this ) ) +{ + d->create( arguments ); +} + +KDSingleApplicationGuard::Private::Private( Policy policy_, KDSingleApplicationGuard * qq ) + : q( qq ), + id( -1 ), + policy( policy_ ), + operational( false ), + exitRequested( false ) +{ +} + +void KDSingleApplicationGuard::Private::create( const QStringList & arguments ) +{ + if ( !QCoreApplication::instance() ) { + qWarning( "KDSingleApplicationGuard: you need to construct a Q(Core)Application before you can construct a KDSingleApplicationGuard" ); + return; + } + + const QString name = QCoreApplication::applicationName(); + if ( name.isEmpty() ) { + qWarning( "KDSingleApplicationGuard: QCoreApplication::applicationName must not be emty" ); + return; + } + + (void)registerInstanceType(); + if ( primaryInstance == 0 ) + primaryInstance = q; + + mem.setKey( name ); + + // if another instance crashed, the shared memory segment is still there on Unix + // the following lines trigger deletion in that case +#ifndef Q_WS_WIN + mem.attach(); + mem.detach(); +#endif + + const bool created = mem.create( sizeof( InstanceRegister ) ); + if( !created ) + { + QString errorMsg; + if( mem.error() != QSharedMemory::NoError && mem.error() != QSharedMemory::AlreadyExists ) + errorMsg += QString::fromLatin1( "QSharedMemomry::create() failed: %1" ).arg( mem.errorString() ); + + if( !mem.attach() ) + { + if( mem.error() != QSharedMemory::NoError ) + errorMsg += QString::fromLatin1( "QSharedMemomry::attach() failed: %1" ).arg( mem.errorString() ); + + qWarning( "KDSingleApplicationGuard: Could neither create nor attach to shared memory segment." ); + qWarning( "%s\n", errorMsg.toLocal8Bit().constData() ); + return; + } + + const int maxWaitMSecs = 1000 * 60; // stop waiting after 60 seconds + QTime waitTimer; + waitTimer.start(); + + // lets wait till the other instance initialized the register + bool initialized = false; + while( !initialized && waitTimer.elapsed() < maxWaitMSecs ) + { + const KDLockedSharedMemoryPointer< InstanceRegister > instances( &mem ); + initialized = instances->isValid(); +#ifdef Q_WS_WIN + ::Sleep(20); +#else + usleep(20000); +#endif + } + + const KDLockedSharedMemoryPointer< InstanceRegister > instances( &mem ); + if ( instances->version != 0 ) { + qWarning( "KDSingleApplicationGuard: Detected version mismatch. " + "Highest supported version: %ud, actual version: %ud", + KDSINGLEAPPLICATIONGUARD_SHM_VERSION, instances->version ); + return; + } + + } + + + KDLockedSharedMemoryPointer< InstanceRegister > instances( &mem ); + + if( !created ) + { + assert( instances->isValid() ); + + // we're _not_ the first instance + // but the + bool killOurSelf = false; + + // find a new slot... + id = std::find( instances->info, instances->info + instances->maxInstances, ProcessInfo() ) - instances->info; + ProcessInfo& info = instances->info[ id ]; + info = ProcessInfo( NewInstance, arguments, QCoreApplication::applicationPid() ); + killOurSelf = instances->policy == AutoKillOtherInstances; + policy = static_cast( instances->policy ); + + // but the signal that we tried to start was sent to the primary application + if( killOurSelf ) + exitRequested = true; + } + else + { + // ok.... we are the first instance + new ( instances.get() ) InstanceRegister( policy ); // create a new list (in shared memory) + id = 0; // our id = 0 + // and we've no command + instances->info[ 0 ] = ProcessInfo( NoCommand, arguments, QCoreApplication::applicationPid() ); + } + +#ifndef Q_WS_WIN + ::signal( SIGINT, SIGINT_handler ); +#endif + + // now listen for commands + timer.start( 750, q ); + + operational = true; +} + +/*! + Destroys this SingleApplicationGuard. + If this instance has been the primary instance and no other instance is existing anymore, + the application is shut down completely. Otherwise the destructor selects another instance to + be the primary instances. */ KDSingleApplicationGuard::~KDSingleApplicationGuard() { @@ -359,14 +838,48 @@ KDSingleApplicationGuard::~KDSingleApplicationGuard() } /*! - * \property KDSingleApplicationGuard::primaryInstance - * Determines wheter this instance is the primary instance. - * The primary instance is the first instance which was started or an instance which - * got selected by KDSingleApplicationGuard's destructor, when the primary instance was - * shut down. - * - * Get this property's value using %isPrimaryInstance(), and monitor changes to it - * using becamePrimaryInstance(). + \property KDSingleApplicationGuard::operational + + Contains whether this KDSingleApplicationGuard is operational. + + A non-operational KDSingleApplicationGuard cannot be used in any meaningful way. + + Reasons for a KDSingleApplicationGuard being non-operational include: + \li it was constructed before QApplication (or at least QCoreApplication) was constructed + \li it failed to create or attach to the shared memory segment that is used for communication + + Get this property's value using %isOperational(). +*/ +bool KDSingleApplicationGuard::isOperational() const +{ + return d->operational; +} + +/*! + \property KDSingleApplicationGuard::exitRequested + + Contains wheter this istance has been requested to exit. This will happen when this instance + was just started, but the policy is AutoKillOtherInstances or by explicitely calling kill on + this instance(). + + Get this property's value using %isExitRequested(). +*/ +bool KDSingleApplicationGuard::isExitRequested() const +{ + return d->exitRequested; +}; + +/*! + \property KDSingleApplicationGuard::primaryInstance + + Contains whether this instance is the primary instance. + + The primary instance is the first instance which was started or else the instance which + got selected by KDSingleApplicationGuard's destructor, when the primary instance was + shut down. + + Get this property's value using %isPrimaryInstance(), and monitor changes to it + using becamePrimaryInstance(). */ bool KDSingleApplicationGuard::isPrimaryInstance() const { @@ -374,12 +887,12 @@ bool KDSingleApplicationGuard::isPrimaryInstance() const } /*! - * \property KDSingleApplicationGuard::Policy - * Specifies the policy KDSingleApplicationGuard is using when new instances are started. - * This can only be set in the primary instance. - * - * Get this property's value using %policy(), set it using %setPolicy(), and monitor changes - * to it using policyChanged(). + \property KDSingleApplicationGuard::policy + Specifies the policy KDSingleApplicationGuard is using when new instances are started. + This can only be set in the primary instance. + + Get this property's value using %policy(), set it using %setPolicy(), and monitor changes + to it using policyChanged(). */ KDSingleApplicationGuard::Policy KDSingleApplicationGuard::policy() const { @@ -388,42 +901,58 @@ KDSingleApplicationGuard::Policy KDSingleApplicationGuard::policy() const void KDSingleApplicationGuard::setPolicy( Policy policy ) { - Q_ASSERT( isPrimaryInstance() ); + if ( !d->checkOperationalPrimary( "setPolicy", "change the policy" ) ) + return; + if( d->policy == policy ) return; d->policy = policy; - emit policyChanged(); + emit policyChanged( policy ); KDLockedSharedMemoryPointer< InstanceRegister > instances( &d->mem ); instances->policy = policy; } /*! - * Returns a list of all currently running instances. + Returns a list of all currently running instances. */ -QList< KDSingleApplicationGuard::Instance > KDSingleApplicationGuard::instances() const +QVector +KDSingleApplicationGuard::instances() const { - QList< Instance > result; + if ( !d->checkOperational( "instances", "report on other instances" ) ) + return QVector(); + + if ( Private::primaryInstance == 0 ) { + Private::primaryInstance = const_cast( this ); + } + + QVector result; const KDLockedSharedMemoryPointer< InstanceRegister > instances( const_cast< QSharedMemory* >( &d->mem ) ); - for( int i = 0; i < KDSINGLEAPPLICATIONGUARD_NUMBER_OF_PROCESSES; ++i ) + for ( int i = 0, end = instances->maxInstances ; i < end ; ++i ) { const ProcessInfo& info = instances->info[ i ]; if( ( info.command & ( FreeInstance | ExitedInstance ) ) == 0 ) - result.push_back( Instance( info.arguments(), info.pid ) ); + { + bool truncated; + const QStringList arguments = info.arguments( &truncated ); + result.push_back( Instance( arguments, truncated, info.pid ) ); + } } return result; } /*! - * Shuts down all other instances. This can only be called from the - * the primary instance. - * Shut down is done gracefully via QCoreApplication::quit(). + Shuts down all other instances. This can only be called from the + the primary instance. + Shut down is done gracefully via QCoreApplication::quit(). */ void KDSingleApplicationGuard::shutdownOtherInstances() { - Q_ASSERT( isPrimaryInstance() ); + if ( !d->checkOperationalPrimary( "shutdownOtherInstances", "shut other instances down" ) ) + return; + KDLockedSharedMemoryPointer< InstanceRegister > instances( &d->mem ); - for( int i = 1; i < KDSINGLEAPPLICATIONGUARD_NUMBER_OF_PROCESSES; ++i ) + for ( int i = 1, end = instances->maxInstances ; i < end ; ++i ) { if( ( instances->info[ i ].command & ( FreeInstance | ExitedInstance ) ) == 0 ) instances->info[ i ].command = ShutDownCommand; @@ -431,58 +960,91 @@ void KDSingleApplicationGuard::shutdownOtherInstances() } /*! - * Kills all other instances. This can only be called from the - * the primary instance. - * Killing is done via exit(1) + Kills all other instances. This can only be called from the + the primary instance. + Killing is done via emitting exitRequested. It's up to the receiving + instance to react properly. */ void KDSingleApplicationGuard::killOtherInstances() { - Q_ASSERT( isPrimaryInstance() ); + if ( !d->checkOperationalPrimary( "killOtherInstances", "kill other instances" ) ) + return; + KDLockedSharedMemoryPointer< InstanceRegister > instances( &d->mem ); - for( int i = 1; i < KDSINGLEAPPLICATIONGUARD_NUMBER_OF_PROCESSES; ++i ) + for ( int i = 1, end = instances->maxInstances ; i < end ; ++i ) { if( ( instances->info[ i ].command & ( FreeInstance | ExitedInstance ) ) == 0 ) instances->info[ i ].command = KillCommand; } } -/*! - * \reimp - */ -void KDSingleApplicationGuard::timerEvent( QTimerEvent* event ) +bool KDSingleApplicationGuard::event( QEvent * event ) { - Q_UNUSED( event ) + if ( event->type() == QEvent::Timer ) { + const QTimerEvent * const te = static_cast( event ); + if ( te->timerId() == d->timer.timerId() ) { + d->poll(); + return true; + } + } + return QObject::event( event ); +} - if( isPrimaryInstance() ) +void KDSingleApplicationGuard::Private::poll() { + + const quint32 now = QDateTime::currentDateTime().toTime_t(); + + if ( primaryInstance == 0 ) { + primaryInstance = q; + } + + if ( q->isPrimaryInstance() ) { // only the primary instance will get notified about new instances - QList< Instance > exitedInstances; - QList< Instance > startedInstances; + QVector< Instance > exitedInstances; + QVector< Instance > startedInstances; { - KDLockedSharedMemoryPointer< InstanceRegister > instances( &d->mem ); + KDLockedSharedMemoryPointer< InstanceRegister > instances( &mem ); - for( int i = 1; i < KDSINGLEAPPLICATIONGUARD_NUMBER_OF_PROCESSES; ++i ) + if( instances->info[ id ].pid != QCoreApplication::applicationPid() ) + { + for ( int i = 1, end = instances->maxInstances ; i < end && id == 0 ; ++i ) + { + if( instances->info[ i ].pid == QCoreApplication::applicationPid() ) + id = i; + } + emit q->becameSecondaryInstance(); + return; + } + + instances->info[ id ].timestamp = now; + + for ( int i = 1, end = instances->maxInstances ; i < end ; ++i ) { ProcessInfo& info = instances->info[ i ]; if( info.command & NewInstance ) { - startedInstances.push_back( Instance( info.arguments(), info.pid ) ); + bool truncated; + const QStringList arguments = info.arguments( &truncated ); + startedInstances.push_back( Instance( arguments, truncated, info.pid ) ); info.command &= ~NewInstance; // clear NewInstance flag } - else if( info.command & ExitedInstance ) + if( info.command & ExitedInstance ) { - exitedInstances.push_back( Instance( info.arguments(), info.pid ) ); + bool truncated; + const QStringList arguments = info.arguments( &truncated ); + exitedInstances.push_back( Instance( arguments, truncated, info.pid ) ); info.command = FreeInstance; // set FreeInstance flag } } } // one signal for every new instance - _after_ the memory segment was unlocked again - for( QList< Instance >::const_iterator it = startedInstances.begin(); it != startedInstances.end(); ++it ) - emit instanceStarted( *it ); - for( QList< Instance >::const_iterator it = exitedInstances.begin(); it != exitedInstances.end(); ++it ) - emit instanceExited( *it ); + for( QVector< Instance >::const_iterator it = startedInstances.constBegin(); it != startedInstances.constEnd(); ++it ) + emit q->instanceStarted( *it ); + for( QVector< Instance >::const_iterator it = exitedInstances.constBegin(); it != exitedInstances.constEnd(); ++it ) + emit q->instanceExited( *it ); } else { @@ -492,58 +1054,83 @@ void KDSingleApplicationGuard::timerEvent( QTimerEvent* event ) bool policyDidChange = false; { - KDLockedSharedMemoryPointer< InstanceRegister > instances( &d->mem ); + KDLockedSharedMemoryPointer< InstanceRegister > instances( &mem ); - policyDidChange = instances->policy != d->policy; - d->policy = instances->policy; + const Policy oldPolicy = policy; + policy = static_cast( instances->policy ); + policyDidChange = policy != oldPolicy; - if( instances->info[ d->id ].command & BecomePrimaryCommand ) + // check for the primary instance health status + if( now - instances->info[ 0 ].timestamp > KDSINGLEAPPLICATIONGUARD_TIMEOUT_SECONDS ) { - // we became primary! - instances->info[ 0 ] = instances->info[ d->id ]; - instances->info[ d->id ] = ProcessInfo(); // change our id to 0 and declare the old slot as free - d->id = 0; - emit becamePrimaryInstance(); + std::swap( instances->info[ 0 ], instances->info[ id ] ); + id = 0; + instances->info[ id ].timestamp = now; + emit q->becamePrimaryInstance(); + instances->info[ id ].command &= ~BecomePrimaryCommand; // afterwards, reset the flag } - killOurSelf = instances->info[ d->id ].command & KillCommand; // check for kill command - shutDownOurSelf = instances->info[ d->id ].command & ShutDownCommand; // check for shut down command - instances->info[ d->id ].command &= ~( KillCommand | ShutDownCommand | BecomePrimaryCommand ); // reset both flags + if( instances->info[ id ].command & BecomePrimaryCommand ) + { + // we became primary! + instances->info[ 0 ] = instances->info[ id ]; + instances->info[ id ] = ProcessInfo(); // change our id to 0 and declare the old slot as free + id = 0; + instances->info[ id ].timestamp = now; + emit q->becamePrimaryInstance(); + } + + if( instances->info[ id ].command & RaiseCommand ) + { + // raise ourself! + emit q->raiseRequested(); + instances->info[ id ].command &= ~RaiseCommand; // afterwards, reset the flag + } + + + killOurSelf = instances->info[ id ].command & KillCommand; // check for kill command + shutDownOurSelf = instances->info[ id ].command & ShutDownCommand; // check for shut down command + instances->info[ id ].command &= ~( KillCommand | ShutDownCommand | BecomePrimaryCommand ); // reset both flags if( killOurSelf ) { - instances->info[ d->id ].command |= ExitedInstance; // upon kill, we have to set the ExitedInstance flag - d->id = -1; // becauso our d'tor won't be called anymore + instances->info[ id ].command |= ExitedInstance; // upon kill, we have to set the ExitedInstance flag + id = -1; // becauso our d'tor won't be called anymore } } if( killOurSelf ) // kill our self takes precedence - exit( 1 ); + { + exitRequested = true; + emit q->exitRequested(); + } else if( shutDownOurSelf ) qApp->quit(); else if( policyDidChange ) - emit policyChanged(); + emit q->policyChanged( policy ); } } +#include "moc_kdsingleapplicationguard.cpp" + #ifdef KDTOOLSCORE_UNITTESTS #include +#include "kdautopointer.h" + #include #include #include #include -Q_DECLARE_METATYPE( KDSingleApplicationGuard::Instance ); - -static void wait( int msec ) +static void wait( int msec, QSignalSpy * spy=0, int expectedCount=INT_MAX ) { QTime t; t.start(); - while( t.elapsed() < msec ) + while ( ( !spy || spy->count() < expectedCount ) && t.elapsed() < msec ) { - qApp->processEvents( QEventLoop::WaitForMoreEvents, msec - t.elapsed() ); + qApp->processEvents( QEventLoop::WaitForMoreEvents, qMax( 10, msec - t.elapsed() ) ); } } @@ -560,19 +1147,33 @@ static std::ostream& operator<<( std::ostream& stream, const QStringList& list ) return stream; } +namespace { + class ApplicationNameSaver { + Q_DISABLE_COPY( ApplicationNameSaver ) + const QString oldname; + public: + explicit ApplicationNameSaver( const QString & name ) + : oldname( QCoreApplication::applicationName() ) + { + QCoreApplication::setApplicationName( name ); + } + ~ApplicationNameSaver() { + QCoreApplication::setApplicationName( oldname ); + } + }; +} KDAB_UNITTEST_SIMPLE( KDSingleApplicationGuard, "kdcoretools" ) { // set it to an unique name - qApp->setApplicationName( QUuid::createUuid().toString() ); + const ApplicationNameSaver saver( QUuid::createUuid().toString() ); - qRegisterMetaType< KDSingleApplicationGuard::Instance >(); - - KDSingleApplicationGuard* guard3 = 0; - QSignalSpy* spy3 = 0; + KDAutoPointer guard3; + KDAutoPointer spy3; + KDAutoPointer spy4; { - KDSingleApplicationGuard guard1( qApp ); + KDSingleApplicationGuard guard1; assertEqual( guard1.policy(), KDSingleApplicationGuard::AutoKillOtherInstances ); assertEqual( guard1.instances().count(), 1 ); assertTrue( guard1.isPrimaryInstance() ); @@ -580,48 +1181,67 @@ KDAB_UNITTEST_SIMPLE( KDSingleApplicationGuard, "kdcoretools" ) { guard1.setPolicy( KDSingleApplicationGuard::NoPolicy ); assertEqual( guard1.policy(), KDSingleApplicationGuard::NoPolicy ); - QSignalSpy spy1( &guard1, SIGNAL( instanceStarted( KDSingleApplicationGuard::Instance ) ) ); + QSignalSpy spy1( &guard1, SIGNAL(instanceStarted(KDSingleApplicationGuard::Instance)) ); - KDSingleApplicationGuard guard2( qApp ); + KDSingleApplicationGuard guard2; assertEqual( guard1.instances().count(), 2 ); assertEqual( guard2.instances().count(), 2 ); assertEqual( guard2.policy(), KDSingleApplicationGuard::NoPolicy ); assertFalse( guard2.isPrimaryInstance() ); - wait( 1000 ); + wait( 1000, &spy1, 1 ); assertEqual( spy1.count(), 1 ); - guard3 = new KDSingleApplicationGuard( qApp ); - spy3 = new QSignalSpy( guard3, SIGNAL( becamePrimaryInstance() ) ); + guard3.reset( new KDSingleApplicationGuard ); + spy3.reset( new QSignalSpy( guard3.get(), SIGNAL(becamePrimaryInstance()) ) ); + spy4.reset( new QSignalSpy( guard3.get(), SIGNAL(instanceExited(KDSingleApplicationGuard::Instance) ) ) ); assertFalse( guard3->isPrimaryInstance() ); } - wait( 1000 ); + wait( 1000, spy3.get(), 1 ); + wait( 1000, spy4.get(), 1 ); assertEqual( spy3->count(), 1 ); assertEqual( guard3->instances().count(), 1 ); assertTrue( guard3->isPrimaryInstance() ); + guard3.reset( new KDSingleApplicationGuard ); - assertEqual( guard3->instances().first().arguments, qApp->arguments() ); + assertEqual( guard3->instances().first().arguments(), qApp->arguments() ); - QSignalSpy spyStarted( guard3, SIGNAL( instanceStarted( KDSingleApplicationGuard::Instance ) ) ); - QSignalSpy spyExited( guard3, SIGNAL( instanceExited( KDSingleApplicationGuard::Instance ) ) ); + QSignalSpy spyStarted( guard3.get(), SIGNAL(instanceStarted(KDSingleApplicationGuard::Instance)) ); + QSignalSpy spyExited( guard3.get(), SIGNAL(instanceExited(KDSingleApplicationGuard::Instance)) ); { - KDSingleApplicationGuard guard1( qApp ); - KDSingleApplicationGuard guard2( qApp ); + KDSingleApplicationGuard guard1; + KDSingleApplicationGuard guard2; - wait( 1000 ); + wait( 1000, &spyStarted, 2 ); assertEqual( spyStarted.count(), 2 ); } - wait( 1000 ); + wait( 1000, &spyExited, 2 ); assertEqual( spyExited.count(), 2 ); - delete spy3; - delete guard3; + spyStarted.clear(); + spyExited.clear(); + + { + // check arguments-too-long handling: + QStringList args; + for ( unsigned int i = 0, end = KDSINGLEAPPLICATIONGUARD_MAX_COMMAND_LINE/16 ; i != end ; ++i ) + args.push_back( QLatin1String( "0123456789ABCDEF" ) ); + KDSingleApplicationGuard guard3( args ); + + wait( 1000, &spyStarted, 1 ); + + const QVector instances = guard3.instances(); + assertEqual( instances.size(), 2 ); + + assertTrue( instances[1].areArgumentsTruncated() ); + } } #endif // KDTOOLSCORE_UNITTESTS -#endif // QT_VERSION < 0x040400 +#endif // QT_NO_SHAREDMEMORY +#endif // QT_VERSION >= 0x040400 || defined(DOXYGEN_RUN) diff --git a/src/libtomahawk/thirdparty/kdsingleapplicationguard/kdsingleapplicationguard.h b/src/libtomahawk/thirdparty/kdsingleapplicationguard/kdsingleapplicationguard.h index 8acc80ac3..5f526e16d 100644 --- a/src/libtomahawk/thirdparty/kdsingleapplicationguard/kdsingleapplicationguard.h +++ b/src/libtomahawk/thirdparty/kdsingleapplicationguard/kdsingleapplicationguard.h @@ -2,24 +2,29 @@ #define __KDTOOLSCORE_KDSINGLEAPPLICATIONGUARD_H__ #include + +#if QT_VERSION >= 0x040400 || defined(DOXYGEN_RUN) +#ifndef QT_NO_SHAREDMEMORY + #include +#include #include "pimpl_ptr.h" #include "DllMacro.h" -class QCoreApplication; +#include -#ifndef Q_WS_WIN -void SIGINT_handler( int sig ); -#endif +template class QVector; +class QCoreApplication; class DLLEXPORT KDSingleApplicationGuard : public QObject { Q_OBJECT -#ifndef Q_WS_WIN - friend void ::SIGINT_handler( int ); -#endif - + Q_ENUMS( Policy ) + Q_PROPERTY( bool operational READ isOperational ) + Q_PROPERTY( bool exitRequested READ isExitRequested ) + Q_PROPERTY( bool primaryInstance READ isPrimaryInstance NOTIFY becamePrimaryInstance ) + Q_PROPERTY( Policy policy READ policy WRITE setPolicy NOTIFY policyChanged ) public: enum Policy { @@ -27,49 +32,112 @@ public: AutoKillOtherInstances = 1 }; - Q_PROPERTY( bool primaryInstance READ isPrimaryInstance NOTIFY becamePrimaryInstance ) - Q_PROPERTY( Policy policy READ policy WRITE setPolicy NOTIFY policyChanged ) - - explicit KDSingleApplicationGuard( QCoreApplication* parent, Policy policy = AutoKillOtherInstances ); + explicit KDSingleApplicationGuard( QObject * parent=0 ); + explicit KDSingleApplicationGuard( Policy policy, QObject * parent=0 ); + explicit KDSingleApplicationGuard( const QStringList & arguments, QObject * parent=0 ); + explicit KDSingleApplicationGuard( const QStringList & arguments, Policy policy, QObject * parent=0 ); ~KDSingleApplicationGuard(); + bool isOperational() const; + + bool isExitRequested() const; + bool isPrimaryInstance() const; - + Policy policy() const; void setPolicy( Policy policy ); - struct Instance - { - Instance( const QStringList& arguments = QStringList(), qint64 pid = -1 ); + class Instance; - QStringList arguments; - qint64 pid; - }; - - QList< Instance > instances() const; + QVector instances() const; Q_SIGNALS: - void instanceStarted( KDSingleApplicationGuard::Instance instance ); - void instanceExited( KDSingleApplicationGuard::Instance instance ); + void instanceStarted( const KDSingleApplicationGuard::Instance & instance ); + void instanceExited( const KDSingleApplicationGuard::Instance & instance ); + void exitRequested(); + void raiseRequested(); void becamePrimaryInstance(); - void policyChanged(); + void becameSecondaryInstance(); + void policyChanged( KDSingleApplicationGuard::Policy policy ); public Q_SLOTS: void shutdownOtherInstances(); void killOtherInstances(); protected: - void timerEvent( QTimerEvent* event ); + /*! \reimp */ bool event( QEvent * event ); private: +#ifndef Q_WS_WIN + static void SIGINT_handler( int ); +#endif + +private: + friend struct ProcessInfo; + class Private; kdtools::pimpl_ptr< Private > d; }; -#if QT_VERSION < 0x040400 -#ifdef Q_CC_GNU -#warning "Can't use KDSingleApplicationGuard with Qt versions prior to 4.4" -#endif -#endif +class DLLEXPORT KDSingleApplicationGuard::Instance { + friend class ::KDSingleApplicationGuard; + friend class ::KDSingleApplicationGuard::Private; + Instance( const QStringList &, bool, qint64 ); +public: + Instance(); + Instance( const Instance & other ); + ~Instance(); -#endif + void swap( Instance & other ) { + std::swap( d, other.d ); + } + + Instance & operator=( Instance other ) { + swap( other ); + return *this; + } + + bool isNull() const { return !d; } + bool isValid() const; + + bool areArgumentsTruncated() const; + + const QStringList & arguments() const; + qint64 pid() const; + + void shutdown(); + void kill(); + void raise(); + +private: + class Private; + Private * d; +}; + +namespace std { + template <> + inline void swap( KDSingleApplicationGuard::Instance & lhs, + KDSingleApplicationGuard::Instance & rhs ) + { + lhs.swap( rhs ); + } +} // namespace std + +QT_BEGIN_NAMESPACE + +template <> +inline void qSwap( KDSingleApplicationGuard::Instance & lhs, + KDSingleApplicationGuard::Instance & rhs ) +{ + lhs.swap( rhs ); +} +Q_DECLARE_METATYPE( KDSingleApplicationGuard::Instance ) +Q_DECLARE_TYPEINFO( KDSingleApplicationGuard::Instance, Q_MOVABLE_TYPE ); + +QT_END_NAMESPACE + + +#endif // QT_NO_SHAREDMEMORY +#endif // QT_VERSION >= 0x040400 || defined(DOXYGEN_RUN) + +#endif /* __KDTOOLSCORE_KDSINGLEAPPLICATIONGUARD_H__ */ diff --git a/src/main.cpp b/src/main.cpp index 08b1cfc6c..dfb01ff1c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -152,7 +152,7 @@ main( int argc, char *argv[] ) #endif #endif - KDSingleApplicationGuard guard( &a, KDSingleApplicationGuard::AutoKillOtherInstances ); + KDSingleApplicationGuard guard( KDSingleApplicationGuard::AutoKillOtherInstances ); QObject::connect( &guard, SIGNAL( instanceStarted( KDSingleApplicationGuard::Instance ) ), &a, SLOT( instanceStarted( KDSingleApplicationGuard::Instance ) ) ); if ( guard.isPrimaryInstance() )