Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: itay-grudev/SingleApplication
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v3.5.1
Choose a base ref
...
head repository: itay-grudev/SingleApplication
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: master
Choose a head ref
  • 7 commits
  • 12 files changed
  • 4 contributors

Commits on Oct 14, 2023

  1. Copy the full SHA
    af2f7a2 View commit details

Commits on Feb 3, 2024

  1. Merge pull request #181 from dschulz/master

    Add an example using CMakes FetchContent module
    itay-grudev authored Feb 3, 2024
    Copy the full SHA
    448a7b5 View commit details

Commits on Feb 4, 2024

  1. Copy the full SHA
    934e8d3 View commit details
  2. Merge pull request #189 from SZinedine/master

    fix Could not find advapi32_LIBRARY
    itay-grudev authored Feb 4, 2024
    Copy the full SHA
    494772e View commit details

Commits on Jan 16, 2025

  1. Copy the full SHA
    f0bc583 View commit details

Commits on Jan 23, 2025

  1. fix Qt5 build on CI for MacOS which requires x86_64 (#204)

    The Qt5 build in the CI is broken, because Qt5 is provided for x86_64
    only, while current macos runners support arm64 only. Setting MacOS to
    version 13 should fix this until it falls out of GitHub Support.
    
    References:
    - jurplel/install-qt-action#238
    - https://github.com/faaxm/spix/blob/master/.github/workflows/build.yml
    bebuch authored Jan 23, 2025
    Copy the full SHA
    565ebd1 View commit details

Commits on Jan 24, 2025

  1. Enable app class independent usage (#203)

    This is a pure extension and fully backwards compatible.
    
    * Adds support for running it as a pre-compiled library while still being able to
    choose my `QXxxApplication` class at build time.
    * Be able to decide at runtime whether to use the single
    instance stuff (without starting a server and so on).
    bebuch authored Jan 24, 2025
    Copy the full SHA
    0ba7b6c View commit details
21 changes: 19 additions & 2 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
@@ -22,7 +22,7 @@ jobs:
platform:
- ubuntu-20.04
- windows-latest
- macos-latest
- macos-13
include:
- qt_version: 6.2.4
additional_arguments: -D QT_DEFAULT_MAJOR_VERSION=6
@@ -32,7 +32,7 @@ jobs:
make: make
CXXFLAGS: -Wall -Wextra -pedantic -Werror
MAKEFLAGS: -j2
- platform: macos-latest
- platform: macos-13
make: make
CXXFLAGS: -Wall -Wextra -pedantic -Werror
MAKEFLAGS: -j3
@@ -80,31 +80,48 @@ jobs:
cmake . ${{ matrix.additional_arguments }}
cmake --build .
- name: Build separate_object example with CMake
working-directory: examples/separate_object/
run: |
cmake . ${{ matrix.additional_arguments }}
cmake --build .
- name: Build windows_raise_widget example with CMake
working-directory: examples/windows_raise_widget/
run: |
cmake . ${{ matrix.additional_arguments }}
cmake --build .
- name: Build basic example with QMake
if: ${{ !contains(matrix.platform, 'macos') }}
working-directory: examples/basic/
run: |
qmake
${{ matrix.make }}
- name: Build calculator example with QMake
if: ${{ !contains(matrix.platform, 'macos') }}
working-directory: examples/calculator/
run: |
qmake
${{ matrix.make }}
- name: Build sending_arguments example with QMake
if: ${{ !contains(matrix.platform, 'macos') }}
working-directory: examples/sending_arguments/
run: |
qmake
${{ matrix.make }}
- name: Build separate_object example with QMake
if: ${{ !contains(matrix.platform, 'macos') }}
working-directory: examples/separate_object/
run: |
qmake
${{ matrix.make }}
- name: Build windows_raise_widget example with QMake
if: ${{ !contains(matrix.platform, 'macos') }}
working-directory: examples/windows_raise_widget/
run: |
qmake
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Changelog

## 3.6.0

* Freestanding mode where `SingleApplication` doesn't derive from `QCodeApplication` _Benjamin Buch_
* CMake install with CMake config files for freestanding mode _Benjamin Buch_

## 3.5.1

* Bug Fix: Maximum QNativeIpcKey key size on macOS. - _Jonas Kvinge_
80 changes: 71 additions & 9 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,19 +1,29 @@
cmake_minimum_required(VERSION 3.12.0)

project(SingleApplication LANGUAGES CXX DESCRIPTION "Replacement for QtSingleApplication")
project(SingleApplication VERSION 3.6.0 LANGUAGES CXX DESCRIPTION "Replacement for QtSingleApplication")

set(CMAKE_AUTOMOC ON)

add_library(${PROJECT_NAME} STATIC
singleapplication.cpp
singleapplication_p.cpp
)
add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME})

# User configurable options
if(NOT QT_DEFAULT_MAJOR_VERSION)
set(QT_DEFAULT_MAJOR_VERSION 5 CACHE STRING "Qt version to use (5 or 6), defaults to 5")
endif()

if(NOT QAPPLICATION_CLASS)
set(QAPPLICATION_CLASS QCoreApplication CACHE STRING "Qt application base class or FreeStandingSingleApplication")
endif()

option(SINGLEAPPLICATION_INSTALL OFF "Enable freestanding mode install including config files")

if(SINGLEAPPLICATION_INSTALL AND NOT QAPPLICATION_CLASS STREQUAL "FreeStandingSingleApplication")
message(FATAL_ERROR "SINGLEAPPLICATION_INSTALL requires QAPPLICATION_CLASS == FreeStandingSingleApplication")
endif()

# Find dependencies
set(QT_COMPONENTS Core Network)
set(QT_LIBRARIES Qt${QT_DEFAULT_MAJOR_VERSION}::Core Qt${QT_DEFAULT_MAJOR_VERSION}::Network)
@@ -24,8 +34,6 @@ if(QAPPLICATION_CLASS STREQUAL QApplication)
elseif(QAPPLICATION_CLASS STREQUAL QGuiApplication)
list(APPEND QT_COMPONENTS Gui)
list(APPEND QT_LIBRARIES Qt${QT_DEFAULT_MAJOR_VERSION}::Gui)
else()
set(QAPPLICATION_CLASS QCoreApplication)
endif()

find_package(Qt${QT_DEFAULT_MAJOR_VERSION} COMPONENTS ${QT_COMPONENTS} REQUIRED)
@@ -38,13 +46,18 @@ endif()
target_link_libraries(${PROJECT_NAME} PUBLIC ${QT_LIBRARIES})

if(WIN32)
find_library(advapi32_LIBRARY advapi32 REQUIRED)
mark_as_advanced(advapi32_LIBRARY)
target_link_libraries(${PROJECT_NAME} PRIVATE ${advapi32_LIBRARY})
target_link_libraries(${PROJECT_NAME} PRIVATE advapi32)
endif()

if(SINGLEAPPLICATION_INSTALL)
target_compile_definitions(${PROJECT_NAME} PRIVATE QAPPLICATION_CLASS=${QAPPLICATION_CLASS})
target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_include_directories(${PROJECT_NAME} INTERFACE $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>)
else()
target_compile_definitions(${PROJECT_NAME} PUBLIC QAPPLICATION_CLASS=${QAPPLICATION_CLASS})
target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
endif()

target_compile_definitions(${PROJECT_NAME} PUBLIC QAPPLICATION_CLASS=${QAPPLICATION_CLASS})
target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_compile_definitions(${PROJECT_NAME} PRIVATE
QT_NO_CAST_TO_ASCII
QT_NO_CAST_FROM_ASCII
@@ -83,3 +96,52 @@ if(DOXYGEN_FOUND)
README.md
)
endif()

if(SINGLEAPPLICATION_INSTALL)
# Create a header veriant where QAPPLICATION_CLASS is replaced with FreeStandingSingleApplication
file(READ "${CMAKE_CURRENT_SOURCE_DIR}/singleapplication.h" SINGLEAPPLICATION_H_CONTENT)

string(REGEX REPLACE
"#ifndef QAPPLICATION_CLASS[^\n]*\n[ \t]*#define QAPPLICATION_CLASS QCoreApplication[^\n]*\n[ \t]*#endif[^\n]*\n"
""
SINGLEAPPLICATION_H_CONTENT
"${SINGLEAPPLICATION_H_CONTENT}")

string(REGEX REPLACE
"#include QT_STRINGIFY\\(QAPPLICATION_CLASS\\)"
"#include \"FreeStandingSingleApplication\""
SINGLEAPPLICATION_H_CONTENT
"${SINGLEAPPLICATION_H_CONTENT}")

string(REPLACE
"QAPPLICATION_CLASS"
"FreeStandingSingleApplication"
SINGLEAPPLICATION_H_CONTENT
"${SINGLEAPPLICATION_H_CONTENT}")

file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/singleapplication.h" "${SINGLEAPPLICATION_H_CONTENT}")

# CMake install
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/singleapplication.h" "SingleApplication" "FreeStandingSingleApplication"
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}")

include(CMakePackageConfigHelpers)
write_basic_package_version_file(
"SingleApplicationConfigVersion.cmake"
VERSION "${PACKAGE_VERSION}"
COMPATIBILITY SameMajorVersion)

configure_file("SingleApplicationConfig.cmake.in" "SingleApplicationConfig.cmake" @ONLY)
install(FILES
"${CMAKE_CURRENT_BINARY_DIR}/SingleApplicationConfig.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/SingleApplicationConfigVersion.cmake"
DESTINATION "lib/cmake/SingleApplication")

install(TARGETS SingleApplication EXPORT SingleApplicationTargets)
install(EXPORT SingleApplicationTargets
FILE "SingleApplicationTargets.cmake"
NAMESPACE "SingleApplication::"
DESTINATION "lib/cmake/SingleApplication")
else()
add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME})
endif()
41 changes: 41 additions & 0 deletions FreeStandingSingleApplication
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright (c) Itay Grudev 2015 - 2023
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// Permission is not granted to use this software or any of the associated files
// as sample data for the purposes of building machine learning models.
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

#ifndef FREE_STANDING_SINGLE_APPLICATION_H
#define FREE_STANDING_SINGLE_APPLICATION_H

#include <QCoreApplication>

/**
* @brief Fake Qt application base class
* Use this as base if you want to use SingleApplication as a free standing object that must be
* explicitly instanciated after your Qt application object.
*
* This enables you to use SingleApplication as a precompiled library and/or to decide at runtime
* if you want to use a SingleApplication instance or not.
*/
struct FreeStandingSingleApplication: QObject{
FreeStandingSingleApplication( int&, char** ) {}
};

#endif
119 changes: 113 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# SingleApplication

[![CI](https://github.com/itay-grudev/SingleApplication/workflows/CI:%20Build%20Test/badge.svg)](https://github.com/itay-grudev/SingleApplication/actions)
[![CI](https://github.com/itay-grudev/SingleApplication/workflows/CI:%20Build%20Test/badge.svg?branch=master)](https://github.com/itay-grudev/SingleApplication/actions)

This is a replacement of the QtSingleApplication for `Qt5` and `Qt6`.

@@ -60,6 +60,50 @@ add_subdirectory(src/third-party/singleapplication)
target_link_libraries(${PROJECT_NAME} SingleApplication::SingleApplication)
```

Directly including this repository as a Git submodule, or even just a shallow copy of the
source code into new projects might not be ideal when using CMake.
Another option is using CMake's `FetchContent` module, available since version `3.11`.
```cmake
# Define the minumun CMake version, as an example 3.24
cmake_minimum_required(VERSION 3.24)
# Include the module
include(FetchContent)
# If using Qt6, override DEFAULT_MAJOR_VERSION
set(QT_DEFAULT_MAJOR_VERSION 6 CACHE STRING "Qt version to use, defaults to 6")
# Set QAPPLICATION_CLASS
set(QAPPLICATION_CLASS QApplication CACHE STRING "Inheritance class for SingleApplication")
# Declare how is the source going to be obtained
FetchContent_Declare(
SingleApplication
GIT_REPOSITORY https://github.com/itay-grudev/SingleApplication
GIT_TAG master
#GIT_TAG e22a6bc235281152b0041ce39d4827b961b66ea6
)
# Fetch the repository and make it available to the build
FetchContent_MakeAvailable(SingleApplication)
# Then simply use find_package as usual
find_package(SingleApplication)
# Finally add it to the target_link_libraries() section
target_link_libraries(ClientePOS PRIVATE
Qt${QT_VERSION_MAJOR}::Widgets
Qt${QT_VERSION_MAJOR}::Network
Qt${QT_VERSION_MAJOR}::Sql
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
@@ -75,6 +119,67 @@ 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
is not `QCoreApplication` as in examples above.

## Freestanding mode

Traditionally, the functionality of this library is implemented as part of the Qt
application class. The base class is defined by the macro `QAPPLICATION_CLASS`.

In freestanding mode, `SingleApplication` is not derived from a Qt application
class. Instead, an instance of a Qt application class is created as normal,
followed by a separate instance of the `SingleApplication` class.

```cpp
#include <QApplication>
#include <SingleApplication.h>

int main( int argc, char* argv[] )
{
// The normal application class with a type of your choice
QApplication app( argc, argv );

// Separate single application object (argc and argv are discarded)
SingleApplication single( argc, argv /*, options ...*/ );

// Do your stuff

return app.exec();
}
```
_Note:_ With the discarded arguments and the class name that sounds like a Qt
application class without being one, this looks like a workaround – it is a
workaround. For 4.x, the single instance functionality could be moved to
something like a `SingleManager` class, which would then be used to implement
`SingleApplication`. This can't be done in 3.x, because moving
`SingleApplication::Mode` to `SingleManager::Mode` would be a breaking change.
To enable the freestanding mode set `QAPPLICATION_CLASS` to
`FreeStandingSingleApplication`. This is a fake base class with no additional
functionality.
The standalone mode allows us to use a precompiled version of this library,
because we don't need the `QAPPLICATION_CLASS` macro to define our Qt application
class at build time. Furthermore, we can use `std::optional<SingleApplication>`
to decide at runtime whether we want single application functionality or not.
Use the standard CMake workflow to create a precompiled static library version,
including CMake config files.
```bash
cmake -DQAPPLICATION_CLASS=FreeStandingSingleApplication -DSINGLEAPPLICATION_INSTALL=ON SingleApplicationDir
cmake --build .
cmake --install
```

This can be used via:

```cmake
find_package(SingleApplication REQUIRED)
target_link_libraries(YourTarget SingleApplication::SingleApplication)
```

_Note:_ The `QAPPLICATION_CLASS` macro is eliminated during CMake install.

## Instance started signal

The `SingleApplication` class implements a `instanceStarted()` signal. You can
@@ -141,11 +246,13 @@ will replace the Primary one even if the Secondary flag has been set.*

## Examples

There are three examples provided in this repository:
There are five examples provided in this repository:

* Basic example that prevents a secondary instance from starting [`examples/basic`](https://github.com/itay-grudev/SingleApplication/tree/master/examples/basic)
* An example of a graphical application raising it's parent window [`examples/calculator`](https://github.com/itay-grudev/SingleApplication/tree/master/examples/calculator)
* A console application sending the primary instance it's command line parameters [`examples/sending_arguments`](https://github.com/itay-grudev/SingleApplication/tree/master/examples/sending_arguments)
* Basic example that prevents a secondary instance from starting [`examples/basic`](examples/basic)
* An example of a graphical application raising it's parent window [`examples/calculator`](examples/calculator)
* A console application sending the primary instance it's command line parameters [`examples/sending_arguments`](examples/sending_arguments)
* A variant of `sending_arguments` where `SingleApplication`is used in freestanding mode [`examples/separate_object`](examples/separate_object)
* A graphical application with Windows specific additions raising it's parent window [`examples/windows_raise_widget`](examples/windows_raise_widget)

## Versioning

@@ -168,7 +275,7 @@ instances running.

## License

This library and it's supporting documentation, with the exception of the Qt
This library and it's supporting documentation, with the exception of the Qt
calculator examples which is distributed under the BSD license, are released
under the terms of `The MIT License (MIT)` with an extra condition, that:

5 changes: 5 additions & 0 deletions SingleApplicationConfig.cmake.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
include(CMakeFindDependencyMacro)

find_dependency(Qt@QT_DEFAULT_MAJOR_VERSION@ COMPONENTS Core Network REQUIRED)

include("${CMAKE_CURRENT_LIST_DIR}/SingleApplicationTargets.cmake")
Loading