Bump singleapplication version

This commit is contained in:
Nicolas Werner 2020-05-25 13:01:37 +02:00
parent 247539cb5a
commit fe45c49e56
13 changed files with 213 additions and 91 deletions

View file

@ -422,7 +422,7 @@ endif()
# single instance functionality # single instance functionality
set(QAPPLICATION_CLASS QApplication CACHE STRING "Inheritance class for SingleApplication") set(QAPPLICATION_CLASS QApplication CACHE STRING "Inheritance class for SingleApplication")
add_subdirectory(third_party/SingleApplication-3.0.19/) add_subdirectory(third_party/SingleApplication-3.1.3.1/)
feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES) feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES)

View file

@ -6,4 +6,11 @@
/examples/basic/basic /examples/basic/basic
/examples/calculator/calculator /examples/calculator/calculator
/examples/sending_arguments/sending_arguments /examples/sending_arguments/sending_arguments
CMakeLists.txt.user /**/CMakeLists.txt.user
/**/CMakeCache.txt
/**/CMakeCache/*
/**/CMakeFiles/*
/**/Makefile
/**/cmake_install.cmake
/**/*_autogen/
libSingleApplication.a

View file

@ -1,6 +1,40 @@
Changelog Changelog
========= =========
If by accident I have forgotten to credit someone in the CHANGELOG, email me and I will fix it.
__3.1.3.1__
---------
* CMake build system improvements
* Fixed Clang Tidy warnings
_Hennadii Chernyshchyk_
__3.1.3__
---------
* Improved `CMakeLists.txt`
_Hennadii Chernyshchyk_
__3.1.2__
---------
* Fix a crash when exiting an application on Android and iOS
_Emeric Grange_
__3.1.1a__
----------
* Added currentUser() method that returns the user the current instance is running as.
_Leander Schulten_
__3.1.0a__
----------
* Added primaryUser() method that returns the user the primary instance is running as.
__3.0.19__ __3.0.19__
---------- ----------

View file

@ -1,45 +1,34 @@
cmake_minimum_required(VERSION 3.1.0) cmake_minimum_required(VERSION 3.7.0)
project(SingleApplication) project(SingleApplication LANGUAGES CXX)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_AUTOMOC ON) set(CMAKE_AUTOMOC ON)
# SingleApplication base class
set(QAPPLICATION_CLASS QCoreApplication CACHE STRING "Inheritance class for SingleApplication")
set_property(CACHE QAPPLICATION_CLASS PROPERTY STRINGS QApplication QGuiApplication QCoreApplication)
# Libary target
add_library(${PROJECT_NAME} STATIC add_library(${PROJECT_NAME} STATIC
singleapplication.cpp singleapplication.cpp
singleapplication_p.cpp singleapplication_p.cpp
) )
add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME})
# Find dependencies # Find dependencies
find_package(Qt5Network) find_package(Qt5 COMPONENTS Network REQUIRED)
target_link_libraries(${PROJECT_NAME} PRIVATE Qt5::Network)
if(QAPPLICATION_CLASS STREQUAL QApplication) if(QAPPLICATION_CLASS STREQUAL QApplication)
find_package(Qt5 COMPONENTS Widgets REQUIRED) find_package(Qt5 COMPONENTS Widgets REQUIRED)
target_link_libraries(${PROJECT_NAME} PUBLIC Qt5::Widgets)
elseif(QAPPLICATION_CLASS STREQUAL QGuiApplication) elseif(QAPPLICATION_CLASS STREQUAL QGuiApplication)
find_package(Qt5 COMPONENTS Gui REQUIRED) find_package(Qt5 COMPONENTS Gui REQUIRED)
target_link_libraries(${PROJECT_NAME} PUBLIC Qt5::Gui)
else() else()
set(QAPPLICATION_CLASS QCoreApplication)
find_package(Qt5 COMPONENTS Core REQUIRED) find_package(Qt5 COMPONENTS Core REQUIRED)
endif() target_link_libraries(${PROJECT_NAME} PUBLIC Qt5::Core)
target_compile_definitions(${PROJECT_NAME} PUBLIC QAPPLICATION_CLASS=${QAPPLICATION_CLASS})
# Link dependencies
target_link_libraries(${PROJECT_NAME} PRIVATE Qt5::Network)
if(QAPPLICATION_CLASS STREQUAL QApplication)
target_link_libraries(${PROJECT_NAME} PRIVATE Qt5::Widgets)
elseif(QAPPLICATION_CLASS STREQUAL QGuiApplication)
target_link_libraries(${PROJECT_NAME} PRIVATE Qt5::Gui)
else()
target_link_libraries(${PROJECT_NAME} PRIVATE Qt5::Core)
endif() endif()
if(WIN32) if(WIN32)
target_link_libraries(${PROJECT_NAME} PRIVATE advapi32) target_link_libraries(${PROJECT_NAME} PRIVATE advapi32)
endif() endif()
target_compile_definitions(${PROJECT_NAME} PUBLIC QAPPLICATION_CLASS=${QAPPLICATION_CLASS})
target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME})

View file

@ -1,6 +1,6 @@
The MIT License (MIT) The MIT License (MIT)
Copyright (c) Itay Grudev 2015 - 2016 Copyright (c) Itay Grudev 2015 - 2020
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View file

@ -1,5 +1,6 @@
SingleApplication SingleApplication
================= =================
[![CI](https://github.com/itay-grudev/SingleApplication/workflows/CI:%20Build%20Test/badge.svg)](https://github.com/itay-grudev/SingleApplication/actions)
This is a replacement of the QtSingleApplication for `Qt5`. This is a replacement of the QtSingleApplication for `Qt5`.
@ -15,18 +16,6 @@ class you specify via the `QAPPLICATION_CLASS` macro (`QCoreApplication` is the
default). Further usage is similar to the use of the `Q[Core|Gui]Application` default). Further usage is similar to the use of the `Q[Core|Gui]Application`
classes. classes.
The library sets up a `QLocalServer` and a `QSharedMemory` block. The first
instance of your Application is your Primary Instance. It would check if the
shared memory block exists and if not it will start a `QLocalServer` and listen
for connections. Each subsequent instance of your application would check if the
shared memory block exists and if it does, it will connect to the QLocalServer
to notify the primary instance that a new instance had been started, after which
it would terminate with status code `0`. In the Primary Instance
`SingleApplication` would emit the `instanceStarted()` signal upon detecting
that a new instance had been started.
The library uses `stdlib` to terminate the program with the `exit()` function.
You can use the library as if you use any other `QCoreApplication` derived You can use the library as if you use any other `QCoreApplication` derived
class: class:
@ -43,8 +32,7 @@ int main( int argc, char* argv[] )
``` ```
To include the library files I would recommend that you add it as a git To include the library files I would recommend that you add it as a git
submodule to your project and include it's contents with a `.pri` file. Here is submodule to your project. Here is how:
how:
```bash ```bash
git submodule add git@github.com:itay-grudev/SingleApplication.git singleapplication git submodule add git@github.com:itay-grudev/SingleApplication.git singleapplication
@ -66,13 +54,27 @@ Then include the subdirectory in your `CMakeLists.txt` project file.
```cmake ```cmake
set(QAPPLICATION_CLASS QApplication CACHE STRING "Inheritance class for SingleApplication") set(QAPPLICATION_CLASS QApplication CACHE STRING "Inheritance class for SingleApplication")
add_subdirectory(src/third-party/singleapplication) add_subdirectory(src/third-party/singleapplication)
target_link_libraries(${PROJECT_NAME} SingleApplication::SingleApplication)
``` ```
The library sets up a `QLocalServer` and a `QSharedMemory` block. The first
instance of your Application is your Primary Instance. It would check if the
shared memory block exists and if not it will start a `QLocalServer` and listen
for connections. Each subsequent instance of your application would check if the
shared memory block exists and if it does, it will connect to the QLocalServer
to notify the primary instance that a new instance had been started, after which
it would terminate with status code `0`. In the Primary Instance
`SingleApplication` would emit the `instanceStarted()` signal upon detecting
that a new instance had been started.
The library uses `stdlib` to terminate the program with the `exit()` function.
Also don't forget to specify which `QCoreApplication` class your app is using if it Also don't forget to specify which `QCoreApplication` class your app is using if it
is not `QCoreApplication` as in examples above. is not `QCoreApplication` as in examples above.
The `Instance Started` signal The `Instance Started` signal
------------------------ -----------------------------
The SingleApplication class implements a `instanceStarted()` signal. You can The SingleApplication class implements a `instanceStarted()` signal. You can
bind to that signal to raise your application's window when a new instance had bind to that signal to raise your application's window when a new instance had
@ -204,6 +206,22 @@ qint64 SingleApplication::primaryPid()
Returns the process ID (PID) of the primary instance. Returns the process ID (PID) of the primary instance.
---
```cpp
QString SingleApplication::primaryUser()
```
Returns the username the primary instance is running as.
---
```cpp
QString SingleApplication::currentUser()
```
Returns the username the current instance is running as.
### Signals ### Signals
```cpp ```cpp

View file

@ -0,0 +1 @@
#include "singleapplication.h"

View file

@ -1,6 +1,6 @@
// The MIT License (MIT) // The MIT License (MIT)
// //
// Copyright (c) Itay Grudev 2015 - 2018 // Copyright (c) Itay Grudev 2015 - 2020
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal // of this software and associated documentation files (the "Software"), to deal
@ -85,7 +85,7 @@ SingleApplication::SingleApplication( int &argc, char *argv[], bool allowSeconda
} }
} }
InstancesInfo* inst = static_cast<InstancesInfo*>( d->memory->data() ); auto *inst = static_cast<InstancesInfo*>( d->memory->data() );
QElapsedTimer time; QElapsedTimer time;
time.start(); time.start();
@ -172,7 +172,19 @@ qint64 SingleApplication::primaryPid()
return d->primaryPid(); return d->primaryPid();
} }
bool SingleApplication::sendMessage( QByteArray message, int timeout ) QString SingleApplication::primaryUser()
{
Q_D(SingleApplication);
return d->primaryUser();
}
QString SingleApplication::currentUser()
{
Q_D(SingleApplication);
return d->getUsername();
}
bool SingleApplication::sendMessage( const QByteArray &message, int timeout )
{ {
Q_D(SingleApplication); Q_D(SingleApplication);

View file

@ -43,7 +43,7 @@ class SingleApplication : public QAPPLICATION_CLASS
{ {
Q_OBJECT Q_OBJECT
typedef QAPPLICATION_CLASS app_t; using app_t = QAPPLICATION_CLASS;
public: public:
/** /**
@ -86,7 +86,7 @@ public:
* @see See the corresponding QAPPLICATION_CLASS constructor for reference * @see See the corresponding QAPPLICATION_CLASS constructor for reference
*/ */
explicit SingleApplication( int &argc, char *argv[], bool allowSecondary = false, Options options = Mode::User, int timeout = 1000 ); explicit SingleApplication( int &argc, char *argv[], bool allowSecondary = false, Options options = Mode::User, int timeout = 1000 );
~SingleApplication(); ~SingleApplication() override;
/** /**
* @brief Returns if the instance is the primary instance * @brief Returns if the instance is the primary instance
@ -112,6 +112,18 @@ public:
*/ */
qint64 primaryPid(); qint64 primaryPid();
/**
* @brief Returns the username of the user running the primary instance
* @returns {QString}
*/
QString primaryUser();
/**
* @brief Returns the username of the current user
* @returns {QString}
*/
QString currentUser();
/** /**
* @brief Sends a message to the primary instance. Returns true on success. * @brief Sends a message to the primary instance. Returns true on success.
* @param {int} timeout - Timeout for connecting * @param {int} timeout - Timeout for connecting
@ -119,7 +131,7 @@ public:
* @note sendMessage() will return false if invoked from the primary * @note sendMessage() will return false if invoked from the primary
* instance. * instance.
*/ */
bool sendMessage( QByteArray message, int timeout = 100 ); bool sendMessage( const QByteArray &message, int timeout = 100 );
Q_SIGNALS: Q_SIGNALS:
void instanceStarted(); void instanceStarted();

View file

@ -0,0 +1,20 @@
QT += core network
CONFIG += c++11
HEADERS += $$PWD/SingleApplication \
$$PWD/singleapplication.h \
$$PWD/singleapplication_p.h
SOURCES += $$PWD/singleapplication.cpp \
$$PWD/singleapplication_p.cpp
INCLUDEPATH += $$PWD
win32 {
msvc:LIBS += Advapi32.lib
gcc:LIBS += -ladvapi32
}
DISTFILES += \
$$PWD/README.md \
$$PWD/CHANGELOG.md \
$$PWD/Windows.md

View file

@ -1,6 +1,6 @@
// The MIT License (MIT) // The MIT License (MIT)
// //
// Copyright (c) Itay Grudev 2015 - 2018 // Copyright (c) Itay Grudev 2015 - 2020
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal // of this software and associated documentation files (the "Software"), to deal
@ -69,19 +69,53 @@ SingleApplicationPrivate::~SingleApplicationPrivate()
delete socket; delete socket;
} }
if( memory != nullptr ) {
memory->lock(); memory->lock();
InstancesInfo* inst = static_cast<InstancesInfo*>(memory->data()); auto *inst = static_cast<InstancesInfo*>(memory->data());
if( server != nullptr ) { if( server != nullptr ) {
server->close(); server->close();
delete server; delete server;
inst->primary = false; inst->primary = false;
inst->primaryPid = -1; inst->primaryPid = -1;
inst->primaryUser[0] = '\0';
inst->checksum = blockChecksum(); inst->checksum = blockChecksum();
} }
memory->unlock(); memory->unlock();
delete memory; delete memory;
} }
}
QString SingleApplicationPrivate::getUsername()
{
#ifdef Q_OS_WIN
wchar_t username[UNLEN + 1];
// Specifies size of the buffer on input
DWORD usernameLength = UNLEN + 1;
if( GetUserNameW( username, &usernameLength ) )
return QString::fromWCharArray( username );
#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0)
return QString::fromLocal8Bit( qgetenv( "USERNAME" ) );
#else
return qEnvironmentVariable( "USERNAME" );
#endif
#endif
#ifdef Q_OS_UNIX
QString username;
uid_t uid = geteuid();
struct passwd *pw = getpwuid( uid );
if( pw )
username = QString::fromLocal8Bit( pw->pw_name );
if ( username.isEmpty() ) {
#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0)
username = QString::fromLocal8Bit( qgetenv( "USER" ) );
#else
username = qEnvironmentVariable( "USER" );
#endif
}
return username;
#endif
}
void SingleApplicationPrivate::genBlockServerName() void SingleApplicationPrivate::genBlockServerName()
{ {
@ -105,28 +139,7 @@ void SingleApplicationPrivate::genBlockServerName()
// User level block requires a user specific data in the hash // User level block requires a user specific data in the hash
if( options & SingleApplication::Mode::User ) { if( options & SingleApplication::Mode::User ) {
#ifdef Q_OS_WIN appData.addData( getUsername().toUtf8() );
wchar_t username [ UNLEN + 1 ];
// Specifies size of the buffer on input
DWORD usernameLength = UNLEN + 1;
if( GetUserNameW( username, &usernameLength ) ) {
appData.addData( QString::fromWCharArray(username).toUtf8() );
} else {
appData.addData( qgetenv("USERNAME") );
}
#endif
#ifdef Q_OS_UNIX
QByteArray username;
uid_t uid = geteuid();
struct passwd *pw = getpwuid(uid);
if( pw ) {
username = pw->pw_name;
}
if( username.isEmpty() ) {
username = qgetenv("USER");
}
appData.addData(username);
#endif
} }
// Replace the backslash in RFC 2045 Base64 [a-zA-Z0-9+/=] to comply with // Replace the backslash in RFC 2045 Base64 [a-zA-Z0-9+/=] to comply with
@ -136,10 +149,11 @@ void SingleApplicationPrivate::genBlockServerName()
void SingleApplicationPrivate::initializeMemoryBlock() void SingleApplicationPrivate::initializeMemoryBlock()
{ {
InstancesInfo* inst = static_cast<InstancesInfo*>( memory->data() ); auto *inst = static_cast<InstancesInfo*>( memory->data() );
inst->primary = false; inst->primary = false;
inst->secondary = 0; inst->secondary = 0;
inst->primaryPid = -1; inst->primaryPid = -1;
inst->primaryUser[0] = '\0';
inst->checksum = blockChecksum(); inst->checksum = blockChecksum();
} }
@ -169,10 +183,12 @@ void SingleApplicationPrivate::startPrimary()
); );
// Reset the number of connections // Reset the number of connections
InstancesInfo* inst = static_cast <InstancesInfo*>( memory->data() ); auto *inst = static_cast <InstancesInfo*>( memory->data() );
inst->primary = true; inst->primary = true;
inst->primaryPid = q->applicationPid(); inst->primaryPid = q->applicationPid();
strncpy( inst->primaryUser, getUsername().toUtf8().data(), 127 );
inst->primaryUser[127] = '\0';
inst->checksum = blockChecksum(); inst->checksum = blockChecksum();
instanceNumber = 0; instanceNumber = 0;
@ -250,13 +266,25 @@ qint64 SingleApplicationPrivate::primaryPid()
qint64 pid; qint64 pid;
memory->lock(); memory->lock();
InstancesInfo* inst = static_cast<InstancesInfo*>( memory->data() ); auto *inst = static_cast<InstancesInfo*>( memory->data() );
pid = inst->primaryPid; pid = inst->primaryPid;
memory->unlock(); memory->unlock();
return pid; return pid;
} }
QString SingleApplicationPrivate::primaryUser()
{
QByteArray username;
memory->lock();
auto *inst = static_cast<InstancesInfo*>( memory->data() );
username = inst->primaryUser;
memory->unlock();
return QString::fromUtf8( username );
}
/** /**
* @brief Executed when a connection has been made to the LocalServer * @brief Executed when a connection has been made to the LocalServer
*/ */

View file

@ -1,6 +1,6 @@
// The MIT License (MIT) // The MIT License (MIT)
// //
// Copyright (c) Itay Grudev 2015 - 2016 // Copyright (c) Itay Grudev 2015 - 2020
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal // of this software and associated documentation files (the "Software"), to deal
@ -42,14 +42,13 @@ struct InstancesInfo {
quint32 secondary; quint32 secondary;
qint64 primaryPid; qint64 primaryPid;
quint16 checksum; quint16 checksum;
char primaryUser[128];
}; };
struct ConnectionInfo { struct ConnectionInfo {
explicit ConnectionInfo() : qint64 msgLen = 0;
msgLen(0), instanceId(0), stage(0) {} quint32 instanceId = 0;
qint64 msgLen; quint8 stage = 0;
quint32 instanceId;
quint8 stage;
}; };
class SingleApplicationPrivate : public QObject { class SingleApplicationPrivate : public QObject {
@ -69,8 +68,9 @@ public:
Q_DECLARE_PUBLIC(SingleApplication) Q_DECLARE_PUBLIC(SingleApplication)
SingleApplicationPrivate( SingleApplication *q_ptr ); SingleApplicationPrivate( SingleApplication *q_ptr );
~SingleApplicationPrivate(); ~SingleApplicationPrivate() override;
QString getUsername();
void genBlockServerName(); void genBlockServerName();
void initializeMemoryBlock(); void initializeMemoryBlock();
void startPrimary(); void startPrimary();
@ -78,6 +78,7 @@ public:
void connectToPrimary(int msecs, ConnectionType connectionType ); void connectToPrimary(int msecs, ConnectionType connectionType );
quint16 blockChecksum(); quint16 blockChecksum();
qint64 primaryPid(); qint64 primaryPid();
QString primaryUser();
void readInitMessageHeader(QLocalSocket *socket); void readInitMessageHeader(QLocalSocket *socket);
void readInitMessageBody(QLocalSocket *socket); void readInitMessageBody(QLocalSocket *socket);