mirror of https://github.com/cutefishos/calamares
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1257 lines
38 KiB
C++
1257 lines
38 KiB
C++
/*
|
|
* SPDX-FileCopyrightText: 2001-2010 Klaralvdalens Datakonsult AB.
|
|
* SPDX-License-Identifier: LGPL-2.0-only
|
|
*
|
|
* The KD Tools Library is Copyright (C) 2001-2010 Klaralvdalens Datakonsult AB.
|
|
*/
|
|
#include "kdsingleapplicationguard.h"
|
|
|
|
#if QT_VERSION >= 0x040400 || defined(DOXYGEN_RUN)
|
|
#ifndef QT_NO_SHAREDMEMORY
|
|
|
|
#include "kdsharedmemorylocker.h"
|
|
#include "kdlockedsharedmemorypointer.h"
|
|
|
|
#include <QVector>
|
|
#include <QCoreApplication>
|
|
#include <QSharedMemory>
|
|
#include <QSharedData>
|
|
#include <QBasicTimer>
|
|
#include <QElapsedTimer>
|
|
#include <QTime>
|
|
|
|
#include <algorithm>
|
|
#include <limits>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <cassert>
|
|
|
|
#ifndef Q_WS_WIN
|
|
#include <csignal>
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
#ifdef Q_WS_WIN
|
|
#include <windows.h>
|
|
#ifndef _SSIZE_T_DEFINED
|
|
typedef signed int ssize_t;
|
|
#endif
|
|
#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 32768
|
|
#endif
|
|
|
|
static unsigned int KDSINGLEAPPLICATIONGUARD_SHM_VERSION = 0;
|
|
|
|
Q_GLOBAL_STATIC_WITH_ARGS( int, registerInstanceType,
|
|
(qRegisterMetaType<KDSingleApplicationGuard::Instance>()) )
|
|
|
|
/*!
|
|
\class KDSingleApplicationGuard::Instance
|
|
\relates KDSingleApplicationGuard
|
|
\ingroup core
|
|
\brief Information about instances a KDSingleApplicationGuard knows about
|
|
|
|
Instance represents instances of applications under
|
|
KDSingleApplicationGuard protection, and allows access to their
|
|
pid() and the arguments() they were started with.
|
|
*/
|
|
|
|
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;
|
|
};
|
|
|
|
struct ProcessInfo;
|
|
|
|
/*!
|
|
\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(const KDSingleApplicationGuard::Instance & instance)
|
|
|
|
This signal is emitted by the primary instance whenever another
|
|
instance \a instance started.
|
|
*/
|
|
|
|
/*!
|
|
\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::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,
|
|
ExitedInstance = 0x01,
|
|
NewInstance = 0x02,
|
|
FreeInstance = 0x04,
|
|
ShutDownCommand = 0x08,
|
|
KillCommand = 0x10,
|
|
BecomePrimaryCommand = 0x20,
|
|
RaiseCommand = 0x40
|
|
};
|
|
|
|
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 )
|
|
: pid( p ),
|
|
command( c ),
|
|
timestamp( 0 ),
|
|
commandline( 0 )
|
|
{
|
|
setArguments( arguments );
|
|
}
|
|
|
|
void setArguments( const QStringList & arguments );
|
|
QStringList arguments( bool * prematureEnd ) const;
|
|
|
|
qint64 pid;
|
|
quint32 command;
|
|
quint32 timestamp;
|
|
char* commandline;
|
|
};
|
|
|
|
static inline bool operator==( const ProcessInfo & lhs, const ProcessInfo & rhs )
|
|
{
|
|
return lhs.command == rhs.command &&
|
|
( lhs.commandline == rhs.commandline || ( lhs.commandline != 0 && rhs.commandline != 0 && ::strcmp( lhs.commandline, rhs.commandline ) == 0 ) );
|
|
}
|
|
|
|
static inline bool operator!=( const ProcessInfo & lhs, const ProcessInfo & rhs )
|
|
{
|
|
return !operator==( lhs, rhs );
|
|
}
|
|
|
|
/*!
|
|
This struct contains information about the managed process system.
|
|
\internal
|
|
*/
|
|
struct InstanceRegister
|
|
{
|
|
explicit InstanceRegister( KDSingleApplicationGuard::Policy policy = KDSingleApplicationGuard::NoPolicy )
|
|
: policy( policy ),
|
|
maxInstances( KDSINGLEAPPLICATIONGUARD_NUMBER_OF_PROCESSES ),
|
|
version( 0 )
|
|
{
|
|
std::fill_n( commandLines, KDSINGLEAPPLICATIONGUARD_MAX_COMMAND_LINE, 0 );
|
|
::memcpy( magicCookie, "kdsingleapp", 12 );
|
|
}
|
|
|
|
/*!
|
|
Returns whether this register was properly initialized by the first instance.
|
|
*/
|
|
bool isValid() const
|
|
{
|
|
return ::strcmp( magicCookie, "kdsingleapp" ) == 0;
|
|
}
|
|
|
|
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 )
|
|
};
|
|
|
|
void ProcessInfo::setArguments( const QStringList & arguments )
|
|
{
|
|
if( commandline != 0 )
|
|
KDSingleApplicationGuard::Private::sharedmem_free( commandline );
|
|
|
|
commandline = 0;
|
|
if( arguments.isEmpty() )
|
|
return;
|
|
|
|
size_t totalsize = MarkerSize;
|
|
for ( const QString& arg : arguments )
|
|
{
|
|
const QByteArray utf8 = arg.toUtf8();
|
|
totalsize += utf8.size() + MarkerSize;
|
|
}
|
|
InstanceRegister* const reg = reinterpret_cast<InstanceRegister*>( 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;
|
|
}
|
|
|
|
char* const commandline = this->commandline + reinterpret_cast<qptrdiff>(reg->commandLines);
|
|
|
|
int argpos = 0;
|
|
for ( const QString & arg : arguments )
|
|
{
|
|
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<quint16>::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<ssize_t>( MarkerSize ) );
|
|
memcpy( commandline + argpos, &RegularEndOfOptions, MarkerSize );
|
|
argpos += MarkerSize;
|
|
}
|
|
|
|
QStringList ProcessInfo::arguments( bool * prematureEnd ) const
|
|
{
|
|
QStringList result;
|
|
if( commandline == 0 )
|
|
{
|
|
if( prematureEnd )
|
|
*prematureEnd = true;
|
|
return result;
|
|
}
|
|
|
|
InstanceRegister* const reg = reinterpret_cast<InstanceRegister*>( KDSingleApplicationGuard::Private::primaryInstance->d->mem.data() );
|
|
const char* const commandline = this->commandline + reinterpret_cast<qptrdiff>(reg->commandLines);
|
|
|
|
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<InstanceRegister*>( KDSingleApplicationGuard::Private::primaryInstance->d->mem.data() );
|
|
char* const heap = reg->commandLines;
|
|
char* const heap_ptr = heap + reinterpret_cast<qptrdiff>(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<InstanceRegister*>( 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<qptrdiff>(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;
|
|
|
|
/*!
|
|
Requests that the instance kills itself (by emitting exitRequested).
|
|
|
|
If the instance has since exited, does nothing.
|
|
|
|
\sa shutdown(), raise()
|
|
*/
|
|
void KDSingleApplicationGuard::Instance::kill()
|
|
{
|
|
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
|
|
|
|
/*!
|
|
\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 ) )
|
|
{
|
|
d->create( QCoreApplication::arguments() );
|
|
}
|
|
|
|
/*!
|
|
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 empty" );
|
|
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
|
|
QElapsedTimer 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<Policy>( 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()
|
|
{
|
|
if( d->id == -1 )
|
|
return;
|
|
|
|
d->shutdownInstance();
|
|
}
|
|
|
|
/*!
|
|
\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
|
|
{
|
|
return d->id == 0;
|
|
}
|
|
|
|
/*!
|
|
\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
|
|
{
|
|
return d->policy;
|
|
}
|
|
|
|
void KDSingleApplicationGuard::setPolicy( Policy policy )
|
|
{
|
|
if ( !d->checkOperationalPrimary( "setPolicy", "change the policy" ) )
|
|
return;
|
|
|
|
if( d->policy == policy )
|
|
return;
|
|
|
|
d->policy = policy;
|
|
emit policyChanged( policy );
|
|
KDLockedSharedMemoryPointer< InstanceRegister > instances( &d->mem );
|
|
instances->policy = policy;
|
|
}
|
|
|
|
/*!
|
|
Returns a list of all currently running instances.
|
|
*/
|
|
QVector<KDSingleApplicationGuard::Instance>
|
|
KDSingleApplicationGuard::instances() const
|
|
{
|
|
if ( !d->checkOperational( "instances", "report on other instances" ) )
|
|
return QVector<Instance>();
|
|
|
|
if ( Private::primaryInstance == 0 ) {
|
|
Private::primaryInstance = const_cast<KDSingleApplicationGuard*>( this );
|
|
}
|
|
|
|
QVector<Instance> result;
|
|
const KDLockedSharedMemoryPointer< InstanceRegister > instances( const_cast< QSharedMemory* >( &d->mem ) );
|
|
for ( int i = 0, end = instances->maxInstances ; i < end ; ++i )
|
|
{
|
|
const ProcessInfo& info = instances->info[ i ];
|
|
if( ( info.command & ( FreeInstance | ExitedInstance ) ) == 0 )
|
|
{
|
|
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().
|
|
*/
|
|
void KDSingleApplicationGuard::shutdownOtherInstances()
|
|
{
|
|
if ( !d->checkOperationalPrimary( "shutdownOtherInstances", "shut other instances down" ) )
|
|
return;
|
|
|
|
KDLockedSharedMemoryPointer< InstanceRegister > instances( &d->mem );
|
|
for ( int i = 1, end = instances->maxInstances ; i < end ; ++i )
|
|
{
|
|
if( ( instances->info[ i ].command & ( FreeInstance | ExitedInstance ) ) == 0 )
|
|
instances->info[ i ].command = ShutDownCommand;
|
|
}
|
|
}
|
|
|
|
/*!
|
|
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()
|
|
{
|
|
if ( !d->checkOperationalPrimary( "killOtherInstances", "kill other instances" ) )
|
|
return;
|
|
|
|
KDLockedSharedMemoryPointer< InstanceRegister > instances( &d->mem );
|
|
for ( int i = 1, end = instances->maxInstances ; i < end ; ++i )
|
|
{
|
|
if( ( instances->info[ i ].command & ( FreeInstance | ExitedInstance ) ) == 0 )
|
|
instances->info[ i ].command = KillCommand;
|
|
}
|
|
}
|
|
|
|
bool KDSingleApplicationGuard::event( QEvent * event )
|
|
{
|
|
if ( event->type() == QEvent::Timer ) {
|
|
const QTimerEvent * const te = static_cast<QTimerEvent*>( event );
|
|
if ( te->timerId() == d->timer.timerId() ) {
|
|
d->poll();
|
|
return true;
|
|
}
|
|
}
|
|
return QObject::event( event );
|
|
}
|
|
|
|
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
|
|
QVector< Instance > exitedInstances;
|
|
QVector< Instance > startedInstances;
|
|
|
|
{
|
|
KDLockedSharedMemoryPointer< InstanceRegister > instances( &mem );
|
|
|
|
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 )
|
|
{
|
|
bool truncated;
|
|
const QStringList arguments = info.arguments( &truncated );
|
|
startedInstances.push_back( Instance( arguments, truncated, info.pid ) );
|
|
info.command &= ~NewInstance; // clear NewInstance flag
|
|
}
|
|
if( info.command & ExitedInstance )
|
|
{
|
|
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( 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
|
|
{
|
|
// do we have a command?
|
|
bool killOurSelf = false;
|
|
bool shutDownOurSelf = false;
|
|
bool policyDidChange = false;
|
|
|
|
{
|
|
KDLockedSharedMemoryPointer< InstanceRegister > instances( &mem );
|
|
|
|
const Policy oldPolicy = policy;
|
|
policy = static_cast<Policy>( instances->policy );
|
|
policyDidChange = policy != oldPolicy;
|
|
|
|
// check for the primary instance health status
|
|
if( now - instances->info[ 0 ].timestamp > KDSINGLEAPPLICATIONGUARD_TIMEOUT_SECONDS )
|
|
{
|
|
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
|
|
}
|
|
|
|
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[ 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
|
|
{
|
|
exitRequested = true;
|
|
emit q->exitRequested();
|
|
}
|
|
else if( shutDownOurSelf )
|
|
qApp->quit();
|
|
else if( policyDidChange )
|
|
emit q->policyChanged( policy );
|
|
}
|
|
}
|
|
|
|
// #include "moc_kdsingleapplicationguard.cpp"
|
|
|
|
#ifdef KDTOOLSCORE_UNITTESTS
|
|
|
|
#include <kdunittest/test.h>
|
|
|
|
#include "kdautopointer.h"
|
|
|
|
#include <iostream>
|
|
|
|
#include <QtCore/QTime>
|
|
#include <QtCore/QUuid>
|
|
#include <QtTest/QSignalSpy>
|
|
|
|
static void wait( int msec, QSignalSpy * spy=0, int expectedCount=INT_MAX )
|
|
{
|
|
QTime t;
|
|
t.start();
|
|
while ( ( !spy || spy->count() < expectedCount ) && t.elapsed() < msec )
|
|
{
|
|
qApp->processEvents( QEventLoop::WaitForMoreEvents, qMax( 10, msec - t.elapsed() ) );
|
|
}
|
|
}
|
|
|
|
static std::ostream& operator<<( std::ostream& stream, const QStringList& list )
|
|
{
|
|
stream << "QStringList(";
|
|
for( QStringList::const_iterator it = list.begin(); it != list.end(); ++it )
|
|
{
|
|
stream << " " << it->toLocal8Bit().data();
|
|
if( it + 1 != list.end() )
|
|
stream << ",";
|
|
}
|
|
stream << " )";
|
|
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
|
|
const ApplicationNameSaver saver( QUuid::createUuid().toString() );
|
|
|
|
KDAutoPointer<KDSingleApplicationGuard> guard3;
|
|
KDAutoPointer<QSignalSpy> spy3;
|
|
KDAutoPointer<QSignalSpy> spy4;
|
|
|
|
{
|
|
KDSingleApplicationGuard guard1;
|
|
assertEqual( guard1.policy(), KDSingleApplicationGuard::AutoKillOtherInstances );
|
|
assertEqual( guard1.instances().count(), 1 );
|
|
assertTrue( guard1.isPrimaryInstance() );
|
|
|
|
guard1.setPolicy( KDSingleApplicationGuard::NoPolicy );
|
|
assertEqual( guard1.policy(), KDSingleApplicationGuard::NoPolicy );
|
|
|
|
QSignalSpy spy1( &guard1, SIGNAL(instanceStarted(KDSingleApplicationGuard::Instance)) );
|
|
|
|
KDSingleApplicationGuard guard2;
|
|
assertEqual( guard1.instances().count(), 2 );
|
|
assertEqual( guard2.instances().count(), 2 );
|
|
assertEqual( guard2.policy(), KDSingleApplicationGuard::NoPolicy );
|
|
assertFalse( guard2.isPrimaryInstance() );
|
|
|
|
wait( 1000, &spy1, 1 );
|
|
|
|
assertEqual( spy1.count(), 1 );
|
|
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, 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() );
|
|
|
|
QSignalSpy spyStarted( guard3.get(), SIGNAL(instanceStarted(KDSingleApplicationGuard::Instance)) );
|
|
QSignalSpy spyExited( guard3.get(), SIGNAL(instanceExited(KDSingleApplicationGuard::Instance)) );
|
|
|
|
{
|
|
KDSingleApplicationGuard guard1;
|
|
KDSingleApplicationGuard guard2;
|
|
|
|
wait( 1000, &spyStarted, 2 );
|
|
|
|
assertEqual( spyStarted.count(), 2 );
|
|
}
|
|
|
|
wait( 1000, &spyExited, 2 );
|
|
assertEqual( spyExited.count(), 2 );
|
|
|
|
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<KDSingleApplicationGuard::Instance> instances = guard3.instances();
|
|
assertEqual( instances.size(), 2 );
|
|
|
|
assertTrue( instances[1].areArgumentsTruncated() );
|
|
}
|
|
}
|
|
|
|
#endif // KDTOOLSCORE_UNITTESTS
|
|
|
|
#endif // QT_NO_SHAREDMEMORY
|
|
#endif // QT_VERSION >= 0x040400 || defined(DOXYGEN_RUN)
|