Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable app class independent usage #203

Merged
merged 6 commits into from
Jan 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 19 additions & 2 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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') }}
itay-grudev marked this conversation as resolved.
Show resolved Hide resolved
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
Expand Down
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_
Expand Down
76 changes: 70 additions & 6 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)
Expand All @@ -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)
Expand All @@ -41,8 +49,15 @@ if(WIN32)
target_link_libraries(${PROJECT_NAME} PRIVATE advapi32)
endif()

target_compile_definitions(${PROJECT_NAME} PUBLIC QAPPLICATION_CLASS=${QAPPLICATION_CLASS})
target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
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} PRIVATE
QT_NO_CAST_TO_ASCII
QT_NO_CAST_FROM_ASCII
Expand Down Expand Up @@ -81,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
73 changes: 68 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,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
Expand Down Expand Up @@ -185,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

Expand All @@ -212,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:

Expand Down
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")
20 changes: 20 additions & 0 deletions examples/separate_object/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
cmake_minimum_required(VERSION 3.7.0)

project(separate_object LANGUAGES CXX)

set(CMAKE_AUTOMOC ON)

# SingleApplication base class
set(QAPPLICATION_CLASS FreeStandingSingleApplication)
add_subdirectory(../.. SingleApplication)

find_package(Qt${QT_DEFAULT_MAJOR_VERSION} COMPONENTS Core REQUIRED)

add_executable(${PROJECT_NAME}
main.cpp
messagereceiver.cpp
messagereceiver.h
main.cpp
)

target_link_libraries(${PROJECT_NAME} SingleApplication::SingleApplication)
Loading
Loading