pax_global_header00006660000000000000000000000064150720000170014502gustar00rootroot0000000000000052 comment=077f21e7004132dbee03fe7b7d7a6d8d7104b316 singleapplication-3.5.4+ds/000077500000000000000000000000001507200001700156225ustar00rootroot00000000000000singleapplication-3.5.4+ds/.github/000077500000000000000000000000001507200001700171625ustar00rootroot00000000000000singleapplication-3.5.4+ds/.github/FUNDING.yml000066400000000000000000000000241507200001700207730ustar00rootroot00000000000000github: itay-grudev singleapplication-3.5.4+ds/.github/workflows/000077500000000000000000000000001507200001700212175ustar00rootroot00000000000000singleapplication-3.5.4+ds/.github/workflows/doxygen.yml000066400000000000000000000017121507200001700234200ustar00rootroot00000000000000name: "Documentation" on: push: branches: - 'master' jobs: doxygen: name: Doxygen runs-on: ubuntu-22.04 steps: - name: Clone repo uses: actions/checkout@v3 - name: Install doxygen and pre-requsites packages run: | sudo apt-get update sudo apt-get install doxygen qtbase5-dev - name: Generate documentation run: | cmake -B build -D SINGLEAPPLICATION_DOCUMENTATION=ON -D DOXYGEN_WARN_AS_ERROR=YES cmake --build build --target SingleApplicationDocumentation find build/html/ -name *.html -type f -exec sed -i 's+https://github.com/jothepro/doxygen-awesome-css+https://github.com/itay-grudev/SingleApplication+g' {} \; - name: Deploy to GitHub pages uses: crazy-max/ghaction-github-pages@v3 with: target_branch: gh-pages build_dir: build/html env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} singleapplication-3.5.4+ds/.github/workflows/main.yml000066400000000000000000000070311507200001700226670ustar00rootroot00000000000000name: "CI: Build Test" on: push: branches-ignore: - "releases/**" paths-ignore: - "**.md" pull_request: paths-ignore: - "**.md" jobs: build: name: Build strategy: matrix: qt_version: - 5.15.0 - 6.2.4 - 6.5.0 platform: - ubuntu-20.04 - windows-latest - macos-13 include: - qt_version: 6.2.4 additional_arguments: -D QT_DEFAULT_MAJOR_VERSION=6 - qt_version: 6.5.0 additional_arguments: -D QT_DEFAULT_MAJOR_VERSION=6 - platform: ubuntu-20.04 make: make CXXFLAGS: -Wall -Wextra -pedantic -Werror MAKEFLAGS: -j2 - platform: macos-13 make: make CXXFLAGS: -Wall -Wextra -pedantic -Werror MAKEFLAGS: -j3 - platform: windows-latest make: nmake CXXFLAGS: /W4 /WX /MP runs-on: ${{ matrix.platform }} env: CXXFLAGS: ${{ matrix.CXXFLAGS }} MAKEFLAGS: ${{ matrix.MAKEFLAGS }} steps: - name: Clone repo uses: actions/checkout@v3 - name: Install Qt uses: jurplel/install-qt-action@v3 with: version: ${{ matrix.qt_version }} - name: Setup MSVC environment for QMake uses: ilammy/msvc-dev-cmd@v1 - name: Build library with CMake run: | cmake . ${{ matrix.additional_arguments }} cmake --build . - name: Build basic example with CMake working-directory: examples/basic/ run: | cmake . ${{ matrix.additional_arguments }} cmake --build . - name: Build calculator example with CMake working-directory: examples/calculator/ run: | cmake . ${{ matrix.additional_arguments }} cmake --build . - name: Build sending_arguments example with CMake working-directory: examples/sending_arguments/ run: | 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 ${{ matrix.make }} singleapplication-3.5.4+ds/.gitignore000066400000000000000000000005621507200001700176150ustar00rootroot00000000000000/.idea /examples/*/*.o /examples/*/Makefile /examples/*/moc_*.cpp /examples/*/moc_predefs.h /examples/*/*.qmake.stash /examples/basic/basic /examples/calculator/calculator /examples/sending_arguments/sending_arguments /**/CMakeLists.txt.user /**/CMakeCache.txt /**/CMakeCache/* /**/CMakeFiles/* /**/Makefile /**/cmake_install.cmake /**/*_autogen/ libSingleApplication.a singleapplication-3.5.4+ds/CHANGELOG.md000066400000000000000000000213261507200001700174370ustar00rootroot00000000000000# 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_ ## 3.5.0 * Switch to the new QNativeIpcKey based QSharedMemory constructor with Qt 6.6 and higher. - _Jonas Kvinge_ ## 3.4.1 * Improved Windows advapi32 link library dependency. - _Frederik Seiffert_ ## 3.4.0 * Provide API for blocking sendMessage. - _Christoph Cullmann_ * New documentation generation using Doxygen * Improved Windows basic widget example * Updated Project License ## 3.3.4 * Fix compilation under Qt 6.2+ and stricter Qt compile settings. - _Christoph Cullmann_ ## 3.3.3 * Support for Qt 6.3+ - Fixed deprecated `QCryptographicHash::addData()` that will only support `QByteArrayView` going further. - _Moody Liu_ ## 3.3.2 * Fixed crash caused by sending a `writeAck` on a removed connection. - _Nicolas Werner_ ## 3.3.1 * Added support for _AppImage_ dynamic executable paths. - _Michael Klein_ ## 3.3.0 * Fixed message fragmentation issue causing crashes and incorrectly / inconsistently received messages. - _Nils Jeisecke_ ## 3.2.0 * Added support for Qt 6 - _Jonas Kvinge_ * Fixed warning in `Qt 5.9` with `min`/`max` functions on Windows - _Nick Korotysh_ * Fix return value of connectToPrimary() when connect is successful - _Jonas Kvinge_ * Fix build issue with MinGW GCC pedantic mode - _Iakov Kirilenko_ * Fixed conversion from `int` to `quint32` and Clang Tidy warnings - _Hennadii Chernyshchyk_ ## 3.1.5 * Improved library stability in edge cases and very rapid process initialisation * Fixed Bug where the shared memory block may have been modified without a lock * Fixed Bug causing `instanceStarted()` to not get emitted when a second instance has been started before the primary has initiated it's `QLocalServer`. ## 3.1.4 * Officially supporting and build-testing against Qt 5.15 * Fixed an MSVC C4996 warning that suggests using `strncpy_s`. _Hennadii Chernyshchyk_ ## 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 * Fixed code warning for depricated functions in Qt 5.10 related to `QTime` and `qrand()`. _Hennadii Chernyshchyk_ _Anton Filimonov_ _Jonas Kvinge_ ## 3.0.18 * Fallback to standard QApplication class on iOS and Android systems where the library is not supported. * Added Build CI tests to verify the library builds successfully on Linux, Windows and MacOS across multiple Qt versions. _Anton Filimonov_ ## 3.0.17 * Fixed compilation warning/error caused by `geteuid()` on unix based systems. _Iakov Kirilenko_ * Added CMake support _Hennadii Chernyshchyk_ ## 3.0.16 * Use geteuid and getpwuid to get username on Unix, fallback to environment variable. _Jonas Kvinge_ ## 3.0.15 * Bug Fix: sendMessage() might return false even though data was actually written. _Jonas Kvinge_ ## 3.0.14 * Fixed uninitialised variables in the `SingleApplicationPrivate` constructor. ## 3.0.13a * Process socket events asynchronously * Fix undefined variable error on Windows _Francis Giraldeau_ ## 3.0.12a * Removed signal handling. ## 3.0.11a * Fixed bug where the message sent by the second process was not received correctly when the message is sent immediately following a connection. _Francis Giraldeau_ * Refactored code and implemented shared memory block consistency checks via `qChecksum()` (CRC-16). * Explicit `qWarning` and `qCritical` when the library is unable to initialise correctly. ## 3.0.10 * Removed C style casts and eliminated all clang warnings. Fixed `instanceId` reading from only one byte in the message deserialization. Cleaned up serialization code using `QDataStream`. Changed connection type to use `quint8 enum` rather than `char`. * Renamed `SingleAppConnectionType` to `ConnectionType`. Added initialization values to all `ConnectionType` enum cases. _Jedidiah Buck McCready_ ## 3.0.9 * Added SingleApplicationPrivate::primaryPid() as a solution to allow bringing the primary window of an application to the foreground on Windows. _Eelco van Dam from Peacs BV_ ## 3.0.8 * Bug fix - changed QApplication::instance() to QCoreApplication::instance() _Evgeniy Bazhenov_ ## 3.0.7a * Fixed compilation error with Mingw32 in MXE thanks to Vitaly Tonkacheyev. * Removed QMutex used for thread safe behaviour. The implementation now uses QCoreApplication::instance() to get an instance to SingleApplication for memory deallocation. ## 3.0.6a * Reverted GetUserName API usage on Windows. Fixed bug with missing library. * Fixed bug in the Calculator example, preventing it's window to be raised on Windows. Special thanks to Charles Gunawan. ## 3.0.5a * Fixed a memory leak in the SingleApplicationPrivate destructor. _Sergei Moiseev_ ## 3.0.4a * Fixed shadow and uninitialised variable warnings. _Paul Walmsley_ ## 3.0.3a * Removed Microsoft Windows specific code for getting username due to multiple problems and compiler differences on Windows platforms. On Windows the shared memory block in User mode now includes the user's home path (which contains the user's username). * Explicitly getting absolute path of the user's home directory as on Unix a relative path (`~`) may be returned. ## 3.0.2a * Fixed bug on Windows when username containing wide characters causes the library to crash. _Le Liu_ ## 3.0.1a * Allows the application path and version to be excluded from the server name hash. The following flags were added for this purpose: * `SingleApplication::Mode::ExcludeAppVersion` * `SingleApplication::Mode::ExcludeAppPath` * Allow a non elevated process to connect to a local server created by an elevated process run by the same user on Windows * Fixes a problem with upper case letters in paths on Windows _Le Liu_ ## v3.0a * Deprecated secondary instances count. * Added a sendMessage() method to send a message to the primary instance. * Added a receivedMessage() signal, emitted when a message is received from a secondary instance. * The SingleApplication constructor's third parameter is now a bool specifying if the current instance should be allowed to run as a secondary instance if there is already a primary instance. * The SingleApplication constructor accept a fourth parameter specifying if the SingleApplication block should be User-wide or System-wide. * SingleApplication no longer relies on `applicationName` and `organizationName` to be set. It instead concatenates all of the following data and computes a `SHA256` hash which is used as the key of the `QSharedMemory` block and the `QLocalServer`. Since at least `applicationFilePath` is always present there is no need to explicitly set any of the following prior to initialising `SingleApplication`. * `QCoreApplication::applicationName` * `QCoreApplication::applicationVersion` * `QCoreApplication::applicationFilePath` * `QCoreApplication::organizationName` * `QCoreApplication::organizationDomain` * User name or home directory path if in User mode * The primary instance is no longer notified when a secondary instance had been started by default. A `Mode` flag for this feature exists. * Added `instanceNumber()` which represents a unique identifier for each secondary instance started. When called from the primary instance will return `0`. ## v2.4 * Stability improvements * Support for secondary instances. * The library now recovers safely after the primary process has crashed and the shared memory had not been deleted. ## v2.3 * Improved pimpl design and inheritance safety. _Vladislav Pyatnichenko_ ## v2.2 * The `QAPPLICATION_CLASS` macro can now be defined in the file including the Single Application header or with a `DEFINES+=` statement in the project file. ## v2.1 * A race condition can no longer occur when starting two processes nearly simultaneously. Fix issue [#3](https://github.com/itay-grudev/SingleApplication/issues/3) ## v2.0 * SingleApplication is now being passed a reference to `argc` instead of a copy. Fix issue [#1](https://github.com/itay-grudev/SingleApplication/issues/1) * Improved documentation. singleapplication-3.5.4+ds/CMakeLists.txt000066400000000000000000000126521507200001700203700ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.12.0) 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 ) # 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) if(QAPPLICATION_CLASS STREQUAL QApplication) list(APPEND QT_COMPONENTS Widgets) list(APPEND QT_LIBRARIES Qt${QT_DEFAULT_MAJOR_VERSION}::Widgets) elseif(QAPPLICATION_CLASS STREQUAL QGuiApplication) list(APPEND QT_COMPONENTS Gui) list(APPEND QT_LIBRARIES Qt${QT_DEFAULT_MAJOR_VERSION}::Gui) endif() find_package(Qt${QT_DEFAULT_MAJOR_VERSION} COMPONENTS ${QT_COMPONENTS} REQUIRED) option(SINGLEAPPLICATION_DOCUMENTATION "Generate Doxygen documentation" OFF) if(SINGLEAPPLICATION_DOCUMENTATION) find_package(Doxygen) endif() target_link_libraries(${PROJECT_NAME} PUBLIC ${QT_LIBRARIES}) if(WIN32) 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 $) 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 QT_NO_URL_CAST_FROM_STRING QT_NO_CAST_FROM_BYTEARRAY QT_USE_QSTRINGBUILDER QT_NO_NARROWING_CONVERSIONS_IN_CONNECT QT_NO_KEYWORDS QT_NO_FOREACH ) if(DOXYGEN_FOUND) # Doxygen theme include(FetchContent) FetchContent_Declare(DoxygenAwesome GIT_REPOSITORY https://github.com/jothepro/doxygen-awesome-css GIT_TAG 4cd62308d825fe0396d2f66ffbab45d0e247724c # 2.0.3 ) FetchContent_MakeAvailable(DoxygenAwesome) FetchContent_GetProperties(DoxygenAwesome SOURCE_DIR DoxygenAwesome_SOURCE_DIR) set(DOXYGEN_USE_MDFILE_AS_MAINPAGE README.md) set(DOXYGEN_GENERATE_TREEVIEW YES) set(DOXYGEN_HTML_HEADER ${DoxygenAwesome_SOURCE_DIR}/doxygen-custom/header.html) set(DOXYGEN_HTML_EXTRA_STYLESHEET ${DoxygenAwesome_SOURCE_DIR}/doxygen-awesome.css) set(DOXYGEN_HTML_EXTRA_FILES ${DoxygenAwesome_SOURCE_DIR}/doxygen-awesome-fragment-copy-button.js ${DoxygenAwesome_SOURCE_DIR}/doxygen-awesome-paragraph-link.js ${DoxygenAwesome_SOURCE_DIR}/doxygen-awesome-darkmode-toggle.js ) doxygen_add_docs(${PROJECT_NAME}Documentation singleapplication.h CHANGELOG.md Windows.md 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() singleapplication-3.5.4+ds/FreeStandingSingleApplication000066400000000000000000000035101507200001700234430ustar00rootroot00000000000000// 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 /** * @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 singleapplication-3.5.4+ds/LICENSE000066400000000000000000000026651507200001700166400ustar00rootroot00000000000000Copyright (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 distribute or redistribute this software, the derivative works of this software, or any of its associated files that was generated in any approach (including building machine learning models), for any purpose, without attributing the source material by including its license. 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. Note: Some of the examples include code not distributed under the terms of the MIT License. singleapplication-3.5.4+ds/README.md000066400000000000000000000235161507200001700171100ustar00rootroot00000000000000# SingleApplication [![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`. Keeps the Primary Instance of your Application and kills each subsequent instances. It can (if enabled) spawn secondary (non-related to the primary) instances and can send data to the primary instance from secondary instances. # [Documentation](https://itay-grudev.github.io/SingleApplication/) You can find the full usage reference and examples [here](https://itay-grudev.github.io/SingleApplication/classSingleApplication.html). ## Usage The `SingleApplication` class inherits from whatever `Q[Core|Gui]Application` 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` classes. You can use the library as if you use any other `QCoreApplication` derived class: ```cpp #include #include int main( int argc, char* argv[] ) { SingleApplication app( argc, argv ); return app.exec(); } ``` To include the library files I would recommend that you add it as a git submodule to your project. Here is how: ```bash git submodule add https://github.com/itay-grudev/SingleApplication.git singleapplication ``` **Qmake:** Then include the `singleapplication.pri` file in your `.pro` project file. ```qmake include(singleapplication/singleapplication.pri) DEFINES += QAPPLICATION_CLASS=QApplication ``` **CMake:** Then include the subdirectory in your `CMakeLists.txt` project file. ```cmake set(QAPPLICATION_CLASS QApplication CACHE STRING "Inheritance class for SingleApplication") 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 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 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 #include 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` 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 bind to that signal to raise your application's window when a new instance had been started, for example. ```cpp // window is a QWindow instance QObject::connect( &app, &SingleApplication::instanceStarted, &window, &QWindow::raise ); ``` Using `SingleApplication::instance()` is a neat way to get the `SingleApplication` instance for binding to it's signals anywhere in your program. _Note:_ On Windows the ability to bring the application windows to the foreground is restricted. See [Windows specific implementations](Windows.md) for a workaround and an example implementation. ## Secondary Instances If you want to be able to launch additional Secondary Instances (not related to your Primary Instance) you have to enable that with the third parameter of the `SingleApplication` constructor. The default is `false` meaning no Secondary Instances. Here is an example of how you would start a Secondary Instance send a message with the command line arguments to the primary instance and then shut down. ```cpp int main(int argc, char *argv[]) { SingleApplication app( argc, argv, true ); if( app.isSecondary() ) { app.sendMessage( app.arguments().join(' ')).toUtf8() ); app.exit( 0 ); } return app.exec(); } ``` _Note:_ A secondary instance won't cause the emission of the `instanceStarted()` signal by default. See `SingleApplication::Mode` for more details.* You can check whether your instance is a primary or secondary with the following methods: ```cpp app.isPrimary(); // or app.isSecondary(); ``` _Note:_ If your Primary Instance is terminated a newly launched instance will replace the Primary one even if the Secondary flag has been set.* ## Examples There are five examples provided in this repository: * 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 Each major version introduces either very significant changes or is not backwards compatible with the previous version. Minor versions only add additional features, bug fixes or performance improvements and are backwards compatible with the previous release. See [CHANGELOG.md](CHANGELOG.md) for more details. ## Implementation The library is implemented with a `QSharedMemory` block which is thread safe and guarantees a race condition will not occur. It also uses a `QLocalSocket` to notify the main process that a new instance had been spawned and thus invoke the `instanceStarted()` signal and for messaging the primary instance. Additionally the library can recover from being forcefully killed on *nix systems and will reset the memory block given that there are no other instances running. ## License 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: ```txt 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. ``` singleapplication-3.5.4+ds/SingleApplication000066400000000000000000000000371507200001700211520ustar00rootroot00000000000000#include "singleapplication.h" singleapplication-3.5.4+ds/SingleApplicationConfig.cmake.in000066400000000000000000000002671507200001700237710ustar00rootroot00000000000000include(CMakeFindDependencyMacro) find_dependency(Qt@QT_DEFAULT_MAJOR_VERSION@ COMPONENTS Core Network REQUIRED) include("${CMAKE_CURRENT_LIST_DIR}/SingleApplicationTargets.cmake") singleapplication-3.5.4+ds/Windows.md000066400000000000000000000017201507200001700175760ustar00rootroot00000000000000# Windows Specifics ## Setting the foreground window In the `instanceStarted()` example in the `README` we demonstrated how an application can bring it's primary instance window whenever a second copy of the application is started. On Windows the ability to bring the application windows to the foreground is restricted, see [AllowSetForegroundWindow()](https://msdn.microsoft.com/en-us/library/windows/desktop/ms632668.aspx) for more details. The background process (the primary instance) can bring its windows to the foreground if it is allowed by the current foreground process (the secondary instance). To bypass this `SingleApplication` must be initialized with the `allowSecondary` parameter set to `true` . If the widget is minimized to Windows task bar, `QWidget::raise()` or `QWidget::show()` can not bring it to the front, you have to use Windows API `ShowWindow()` . You can find [demo code](examples/windows_raise_widget/main.cpp) in the examples directory. singleapplication-3.5.4+ds/singleapplication.cpp000066400000000000000000000226031507200001700220360ustar00rootroot00000000000000// 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. #include #include #include #include #include "singleapplication.h" #include "singleapplication_p.h" /** * @brief Constructor. Checks and fires up LocalServer or closes the program * if another instance already exists * @param argc * @param argv * @param allowSecondary Whether to enable secondary instance support * @param options Optional flags to toggle specific behaviour * @param timeout Maximum time blocking functions are allowed during app load */ SingleApplication::SingleApplication( int &argc, char *argv[], bool allowSecondary, Options options, int timeout, const QString &userData ) : app_t( argc, argv ), d_ptr( new SingleApplicationPrivate( this ) ) { Q_D( SingleApplication ); #if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) // On Android and iOS since the library is not supported fallback to // standard QApplication behaviour by simply returning at this point. qWarning() << "SingleApplication is not supported on Android and iOS systems."; return; #endif // Store the current mode of the program d->options = options; // Add any unique user data if ( ! userData.isEmpty() ) d->addAppData( userData ); // Generating an application ID used for identifying the shared memory // block and QLocalServer d->genBlockServerName(); // To mitigate QSharedMemory issues with large amount of processes // attempting to attach at the same time SingleApplicationPrivate::randomSleep(); #ifdef Q_OS_UNIX // By explicitly attaching it and then deleting it we make sure that the // memory is deleted even after the process has crashed on Unix. #if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0) d->memory = new QSharedMemory( QNativeIpcKey( d->blockServerName ) ); #else d->memory = new QSharedMemory( d->blockServerName ); #endif d->memory->attach(); delete d->memory; #endif // Guarantee thread safe behaviour with a shared memory block. #if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0) d->memory = new QSharedMemory( QNativeIpcKey( d->blockServerName ) ); #else d->memory = new QSharedMemory( d->blockServerName ); #endif // Create a shared memory block if( d->memory->create( sizeof( InstancesInfo ) )){ // Initialize the shared memory block if( ! d->memory->lock() ){ qCritical() << "SingleApplication: Unable to lock memory block after create."; abortSafely(); } d->initializeMemoryBlock(); } else { if( d->memory->error() == QSharedMemory::AlreadyExists ){ // Attempt to attach to the memory segment if( ! d->memory->attach() ){ qCritical() << "SingleApplication: Unable to attach to shared memory block."; abortSafely(); } if( ! d->memory->lock() ){ qCritical() << "SingleApplication: Unable to lock memory block after attach."; abortSafely(); } } else { qCritical() << "SingleApplication: Unable to create block."; abortSafely(); } } auto *inst = static_cast( d->memory->data() ); QElapsedTimer time; time.start(); // Make sure the shared memory block is initialised and in consistent state while( true ){ // If the shared memory block's checksum is valid continue if( d->blockChecksum() == inst->checksum ) break; // If more than 5s have elapsed, assume the primary instance crashed and // assume it's position if( time.elapsed() > 5000 ){ qWarning() << "SingleApplication: Shared memory block has been in an inconsistent state from more than 5s. Assuming primary instance failure."; d->initializeMemoryBlock(); } // Otherwise wait for a random period and try again. The random sleep here // limits the probability of a collision between two racing apps and // allows the app to initialise faster if( ! d->memory->unlock() ){ qDebug() << "SingleApplication: Unable to unlock memory for random wait."; qDebug() << d->memory->errorString(); } SingleApplicationPrivate::randomSleep(); if( ! d->memory->lock() ){ qCritical() << "SingleApplication: Unable to lock memory after random wait."; abortSafely(); } } if( inst->primary == false ){ d->startPrimary(); if( ! d->memory->unlock() ){ qDebug() << "SingleApplication: Unable to unlock memory after primary start."; qDebug() << d->memory->errorString(); } return; } // Check if another instance can be started if( allowSecondary ){ d->startSecondary(); if( d->options & Mode::SecondaryNotification ){ d->connectToPrimary( timeout, SingleApplicationPrivate::SecondaryInstance ); } if( ! d->memory->unlock() ){ qDebug() << "SingleApplication: Unable to unlock memory after secondary start."; qDebug() << d->memory->errorString(); } return; } if( ! d->memory->unlock() ){ qDebug() << "SingleApplication: Unable to unlock memory at end of execution."; qDebug() << d->memory->errorString(); } d->connectToPrimary( timeout, SingleApplicationPrivate::NewInstance ); delete d; ::exit( EXIT_SUCCESS ); } SingleApplication::~SingleApplication() { Q_D( SingleApplication ); delete d; } /** * Checks if the current application instance is primary. * @return Returns true if the instance is primary, false otherwise. */ bool SingleApplication::isPrimary() const { Q_D( const SingleApplication ); return d->server != nullptr; } /** * Checks if the current application instance is secondary. * @return Returns true if the instance is secondary, false otherwise. */ bool SingleApplication::isSecondary() const { Q_D( const SingleApplication ); return d->server == nullptr; } /** * Allows you to identify an instance by returning unique consecutive instance * ids. It is reset when the first (primary) instance of your app starts and * only incremented afterwards. * @return Returns a unique instance id. */ quint32 SingleApplication::instanceId() const { Q_D( const SingleApplication ); return d->instanceNumber; } /** * Returns the OS PID (Process Identifier) of the process running the primary * instance. Especially useful when SingleApplication is coupled with OS. * specific APIs. * @return Returns the primary instance PID. */ qint64 SingleApplication::primaryPid() const { Q_D( const SingleApplication ); return d->primaryPid(); } /** * Returns the username the primary instance is running as. * @return Returns the username the primary instance is running as. */ QString SingleApplication::primaryUser() const { Q_D( const SingleApplication ); return d->primaryUser(); } /** * Returns the username the current instance is running as. * @return Returns the username the current instance is running as. */ QString SingleApplication::currentUser() const { return SingleApplicationPrivate::getUsername(); } /** * Sends message to the Primary Instance. * @param message The message to send. * @param timeout the maximum timeout in milliseconds for blocking functions. * @param sendMode mode of operation * @return true if the message was sent successfuly, false otherwise. */ bool SingleApplication::sendMessage( const QByteArray &message, int timeout, SendMode sendMode ) { Q_D( SingleApplication ); // Nobody to connect to if( isPrimary() ) return false; // Make sure the socket is connected if( ! d->connectToPrimary( timeout, SingleApplicationPrivate::Reconnect ) ) return false; return d->writeConfirmedMessage( timeout, message, sendMode ); } /** * Cleans up the shared memory block and exits with a failure. * This function halts program execution. */ void SingleApplication::abortSafely() { Q_D( SingleApplication ); qCritical() << "SingleApplication: " << d->memory->error() << d->memory->errorString(); delete d; ::exit( EXIT_FAILURE ); } QStringList SingleApplication::userData() const { Q_D( const SingleApplication ); return d->appData(); } singleapplication-3.5.4+ds/singleapplication.h000066400000000000000000000146631507200001700215120ustar00rootroot00000000000000// 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 SINGLE_APPLICATION_H #define SINGLE_APPLICATION_H #include #include #ifndef QAPPLICATION_CLASS #define QAPPLICATION_CLASS QCoreApplication #endif #include QT_STRINGIFY(QAPPLICATION_CLASS) class SingleApplicationPrivate; /** * @brief Handles multiple instances of the same * Application * @see QCoreApplication */ class SingleApplication : public QAPPLICATION_CLASS { Q_OBJECT using app_t = QAPPLICATION_CLASS; public: /** * @brief Mode of operation of `SingleApplication`. * Whether the block should be user-wide or system-wide and whether the * primary instance should be notified when a secondary instance had been * started. * @note Operating system can restrict the shared memory blocks to the same * user, in which case the User/System modes will have no effect and the * block will be user wide. */ enum Mode { /** The `SingleApplication` block should apply user wide * (this adds user specific data to the key used for the shared memory and server name) * */ User = 1 << 0, /** * The `SingleApplication` block applies system-wide. */ System = 1 << 1, /** * Whether to trigger `instanceStarted()` even whenever secondary instances are started */ SecondaryNotification = 1 << 2, /** * Excludes the application version from the server name (and memory block) hash */ ExcludeAppVersion = 1 << 3, /** * Excludes the application path from the server name (and memory block) hash */ ExcludeAppPath = 1 << 4 }; Q_DECLARE_FLAGS(Options, Mode) /** * @brief Intitializes a `SingleApplication` instance with argc command line * arguments in argv * @arg argc - Number of arguments in argv * @arg argv - Supplied command line arguments * @arg allowSecondary - Whether to start the instance as secondary * if there is already a primary instance. * @arg mode - Whether for the `SingleApplication` block to be applied * User wide or System wide. * @arg timeout - Timeout to wait in milliseconds. * @note argc and argv may be changed as Qt removes arguments that it * recognizes * @note `Mode::SecondaryNotification` only works if set on both the primary * instance and the secondary instance. * @note The timeout is just a hint for the maximum time of blocking * operations. It does not guarantee that the `SingleApplication` * initialisation will be completed in given time, though is a good hint. * Usually 4*timeout would be the worst case (fail) scenario. * @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, const QString &userData = {} ); ~SingleApplication() override; /** * @brief Checks if the instance is primary instance * @returns `true` if the instance is primary */ bool isPrimary() const; /** * @brief Checks if the instance is a secondary instance * @returns `true` if the instance is secondary */ bool isSecondary() const; /** * @brief Returns a unique identifier for the current instance * @returns instance id */ quint32 instanceId() const; /** * @brief Returns the process ID (PID) of the primary instance * @returns pid */ qint64 primaryPid() const; /** * @brief Returns the username of the user running the primary instance * @returns user name */ QString primaryUser() const; /** * @brief Returns the username of the current user * @returns user name */ QString currentUser() const; /** * @brief Mode of operation of sendMessage. */ enum SendMode { NonBlocking, /** Do not wait for the primary instance termination and return immediately */ BlockUntilPrimaryExit, /** Wait until the primary instance is terminated */ }; /** * @brief Sends a message to the primary instance * @param message data to send * @param timeout timeout for connecting * @param sendMode - Mode of operation * @returns `true` on success * @note sendMessage() will return false if invoked from the primary instance */ bool sendMessage( const QByteArray &message, int timeout = 100, SendMode sendMode = NonBlocking ); /** * @brief Get the set user data. * @returns user data */ QStringList userData() const; Q_SIGNALS: /** * @brief Triggered whenever a new instance had been started, * except for secondary instances if the `Mode::SecondaryNotification` flag is not specified */ void instanceStarted(); /** * @brief Triggered whenever there is a message received from a secondary instance */ void receivedMessage( quint32 instanceId, QByteArray message ); private: SingleApplicationPrivate *d_ptr; Q_DECLARE_PRIVATE(SingleApplication) void abortSafely(); }; Q_DECLARE_OPERATORS_FOR_FLAGS(SingleApplication::Options) #endif // SINGLE_APPLICATION_H singleapplication-3.5.4+ds/singleapplication.pri000066400000000000000000000006021507200001700220410ustar00rootroot00000000000000QT += 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 singleapplication-3.5.4+ds/singleapplication_p.cpp000066400000000000000000000412241507200001700223550ustar00rootroot00000000000000// 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. // // W A R N I N G !!! // ----------------- // // This file is not part of the SingleApplication API. It is used purely as an // implementation detail. This header file may change from version to // version without notice, or may even be removed. // #include #include #include #include #include #include #include #include #include #include #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) #include #else #include #endif #include "singleapplication.h" #include "singleapplication_p.h" #ifdef Q_OS_UNIX #include #include #include #endif #ifdef Q_OS_WIN #ifndef NOMINMAX #define NOMINMAX 1 #endif #include #include #endif SingleApplicationPrivate::SingleApplicationPrivate( SingleApplication *q_ptr ) : q_ptr( q_ptr ) { server = nullptr; socket = nullptr; memory = nullptr; instanceNumber = 0; } SingleApplicationPrivate::~SingleApplicationPrivate() { if( socket != nullptr ){ socket->close(); delete socket; } if( memory != nullptr ){ memory->lock(); auto *inst = static_cast(memory->data()); if( server != nullptr ){ server->close(); delete server; inst->primary = false; inst->primaryPid = -1; inst->primaryUser[0] = '\0'; inst->checksum = blockChecksum(); } memory->unlock(); 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() { #ifdef Q_OS_MACOS // Maximum key size on macOS is PSHMNAMLEN (31). QCryptographicHash appData( QCryptographicHash::Md5 ); #else QCryptographicHash appData( QCryptographicHash::Sha256 ); #endif #if QT_VERSION < QT_VERSION_CHECK(6, 3, 0) appData.addData( "SingleApplication", 17 ); #else appData.addData( QByteArrayView{"SingleApplication"} ); #endif appData.addData( QCoreApplication::applicationName().toUtf8() ); appData.addData( QCoreApplication::organizationName().toUtf8() ); appData.addData( QCoreApplication::organizationDomain().toUtf8() ); if ( ! appDataList.isEmpty() ) appData.addData( appDataList.join(QString()).toUtf8() ); if( ! (options & SingleApplication::Mode::ExcludeAppVersion) ){ appData.addData( QCoreApplication::applicationVersion().toUtf8() ); } if( ! (options & SingleApplication::Mode::ExcludeAppPath) ){ #if defined(Q_OS_WIN) appData.addData( QCoreApplication::applicationFilePath().toLower().toUtf8() ); #elif defined(Q_OS_LINUX) // If the application is running as an AppImage then the APPIMAGE env var should be used // instead of applicationPath() as each instance is launched with its own executable path const QByteArray appImagePath = qgetenv( "APPIMAGE" ); if( appImagePath.isEmpty() ){ // Not running as AppImage: use path to executable file appData.addData( QCoreApplication::applicationFilePath().toUtf8() ); } else { // Running as AppImage: Use absolute path to AppImage file appData.addData( appImagePath ); }; #else appData.addData( QCoreApplication::applicationFilePath().toUtf8() ); #endif } // User level block requires a user specific data in the hash if( options & SingleApplication::Mode::User ){ appData.addData( getUsername().toUtf8() ); } // Replace the backslash in RFC 2045 Base64 [a-zA-Z0-9+/=] to comply with // server naming requirements. blockServerName = QString::fromUtf8(appData.result().toBase64().replace("/", "_")); } void SingleApplicationPrivate::initializeMemoryBlock() const { auto *inst = static_cast( memory->data() ); inst->primary = false; inst->secondary = 0; inst->primaryPid = -1; inst->primaryUser[0] = '\0'; inst->checksum = blockChecksum(); } void SingleApplicationPrivate::startPrimary() { // Reset the number of connections auto *inst = static_cast ( memory->data() ); inst->primary = true; inst->primaryPid = QCoreApplication::applicationPid(); qstrncpy( inst->primaryUser, getUsername().toUtf8().data(), sizeof(inst->primaryUser) ); inst->checksum = blockChecksum(); instanceNumber = 0; // Successful creation means that no main process exists // So we start a QLocalServer to listen for connections QLocalServer::removeServer( blockServerName ); server = new QLocalServer(); // Restrict access to the socket according to the // SingleApplication::Mode::User flag on User level or no restrictions if( options & SingleApplication::Mode::User ){ server->setSocketOptions( QLocalServer::UserAccessOption ); } else { server->setSocketOptions( QLocalServer::WorldAccessOption ); } server->listen( blockServerName ); QObject::connect( server, &QLocalServer::newConnection, this, &SingleApplicationPrivate::slotConnectionEstablished ); } void SingleApplicationPrivate::startSecondary() { auto *inst = static_cast ( memory->data() ); inst->secondary += 1; inst->checksum = blockChecksum(); instanceNumber = inst->secondary; } bool SingleApplicationPrivate::connectToPrimary( int msecs, ConnectionType connectionType ) { QElapsedTimer time; time.start(); // Connect to the Local Server of the Primary Instance if not already // connected. if( socket == nullptr ){ socket = new QLocalSocket(); } if( socket->state() == QLocalSocket::ConnectedState ) return true; if( socket->state() != QLocalSocket::ConnectedState ){ while( true ){ randomSleep(); if( socket->state() != QLocalSocket::ConnectingState ) socket->connectToServer( blockServerName ); if( socket->state() == QLocalSocket::ConnectingState ){ socket->waitForConnected( static_cast(msecs - time.elapsed()) ); } // If connected break out of the loop if( socket->state() == QLocalSocket::ConnectedState ) break; // If elapsed time since start is longer than the method timeout return if( time.elapsed() >= msecs ) return false; } } // Initialisation message according to the SingleApplication protocol QByteArray initMsg; QDataStream writeStream(&initMsg, QIODevice::WriteOnly); #if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)) writeStream.setVersion(QDataStream::Qt_5_6); #endif writeStream << blockServerName.toLatin1(); writeStream << static_cast(connectionType); writeStream << instanceNumber; #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) quint16 checksum = qChecksum(QByteArray(initMsg.constData(), static_cast(initMsg.length()))); #else quint16 checksum = qChecksum(initMsg.constData(), static_cast(initMsg.length())); #endif writeStream << checksum; return writeConfirmedMessage( static_cast(msecs - time.elapsed()), initMsg ); } void SingleApplicationPrivate::writeAck( QLocalSocket *sock ) { sock->putChar('\n'); } bool SingleApplicationPrivate::writeConfirmedMessage (int msecs, const QByteArray &msg, SingleApplication::SendMode sendMode) { QElapsedTimer time; time.start(); // Frame 1: The header indicates the message length that follows QByteArray header; QDataStream headerStream(&header, QIODevice::WriteOnly); #if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)) headerStream.setVersion(QDataStream::Qt_5_6); #endif headerStream << static_cast ( msg.length() ); if( ! writeConfirmedFrame( static_cast(msecs - time.elapsed()), header )) return false; // Frame 2: The message const bool result = writeConfirmedFrame( static_cast(msecs - time.elapsed()), msg ); // Block if needed if (socket && sendMode == SingleApplication::BlockUntilPrimaryExit) socket->waitForDisconnected(-1); return result; } bool SingleApplicationPrivate::writeConfirmedFrame( int msecs, const QByteArray &msg ) { socket->write( msg ); socket->flush(); bool result = socket->waitForReadyRead( msecs ); // await ack byte if (result) { socket->read( 1 ); return true; } return false; } quint16 SingleApplicationPrivate::blockChecksum() const { #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) quint16 checksum = qChecksum(QByteArray(static_cast(memory->constData()), offsetof(InstancesInfo, checksum))); #else quint16 checksum = qChecksum(static_cast(memory->constData()), offsetof(InstancesInfo, checksum)); #endif return checksum; } qint64 SingleApplicationPrivate::primaryPid() const { qint64 pid; memory->lock(); auto *inst = static_cast( memory->data() ); pid = inst->primaryPid; memory->unlock(); return pid; } QString SingleApplicationPrivate::primaryUser() const { QByteArray username; memory->lock(); auto *inst = static_cast( memory->data() ); username = inst->primaryUser; memory->unlock(); return QString::fromUtf8( username ); } /** * @brief Executed when a connection has been made to the LocalServer */ void SingleApplicationPrivate::slotConnectionEstablished() { QLocalSocket *nextConnSocket = server->nextPendingConnection(); connectionMap.insert(nextConnSocket, ConnectionInfo()); QObject::connect(nextConnSocket, &QLocalSocket::aboutToClose, this, [nextConnSocket, this](){ auto &info = connectionMap[nextConnSocket]; this->slotClientConnectionClosed( nextConnSocket, info.instanceId ); } ); QObject::connect(nextConnSocket, &QLocalSocket::disconnected, nextConnSocket, &QLocalSocket::deleteLater); QObject::connect(nextConnSocket, &QLocalSocket::destroyed, this, [nextConnSocket, this](){ connectionMap.remove(nextConnSocket); } ); QObject::connect(nextConnSocket, &QLocalSocket::readyRead, this, [nextConnSocket, this](){ auto &info = connectionMap[nextConnSocket]; switch(info.stage){ case StageInitHeader: readMessageHeader( nextConnSocket, StageInitBody ); break; case StageInitBody: readInitMessageBody(nextConnSocket); break; case StageConnectedHeader: readMessageHeader( nextConnSocket, StageConnectedBody ); break; case StageConnectedBody: this->slotDataAvailable( nextConnSocket, info.instanceId ); break; default: break; }; } ); } void SingleApplicationPrivate::readMessageHeader( QLocalSocket *sock, SingleApplicationPrivate::ConnectionStage nextStage ) { if (!connectionMap.contains( sock )){ return; } if( sock->bytesAvailable() < ( qint64 )sizeof( quint64 ) ){ return; } QDataStream headerStream( sock ); #if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)) headerStream.setVersion( QDataStream::Qt_5_6 ); #endif // Read the header to know the message length quint64 msgLen = 0; headerStream >> msgLen; ConnectionInfo &info = connectionMap[sock]; info.stage = nextStage; info.msgLen = msgLen; writeAck( sock ); } bool SingleApplicationPrivate::isFrameComplete( QLocalSocket *sock ) { if (!connectionMap.contains( sock )){ return false; } ConnectionInfo &info = connectionMap[sock]; if( sock->bytesAvailable() < ( qint64 )info.msgLen ){ return false; } return true; } void SingleApplicationPrivate::readInitMessageBody( QLocalSocket *sock ) { Q_Q(SingleApplication); if( !isFrameComplete( sock ) ) return; // Read the message body QByteArray msgBytes = sock->readAll(); QDataStream readStream(msgBytes); #if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)) readStream.setVersion( QDataStream::Qt_5_6 ); #endif // server name QByteArray latin1Name; readStream >> latin1Name; // connection type ConnectionType connectionType = InvalidConnection; quint8 connTypeVal = InvalidConnection; readStream >> connTypeVal; connectionType = static_cast ( connTypeVal ); // instance id quint32 instanceId = 0; readStream >> instanceId; // checksum quint16 msgChecksum = 0; readStream >> msgChecksum; #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) const quint16 actualChecksum = qChecksum(QByteArray(msgBytes.constData(), static_cast(msgBytes.length() - sizeof(quint16)))); #else const quint16 actualChecksum = qChecksum(msgBytes.constData(), static_cast(msgBytes.length() - sizeof(quint16))); #endif bool isValid = readStream.status() == QDataStream::Ok && QLatin1String(latin1Name) == blockServerName && msgChecksum == actualChecksum; if( !isValid ){ sock->close(); return; } ConnectionInfo &info = connectionMap[sock]; info.instanceId = instanceId; info.stage = StageConnectedHeader; if( connectionType == NewInstance || ( connectionType == SecondaryInstance && options & SingleApplication::Mode::SecondaryNotification ) ) { Q_EMIT q->instanceStarted(); } writeAck( sock ); } void SingleApplicationPrivate::slotDataAvailable( QLocalSocket *dataSocket, quint32 instanceId ) { Q_Q(SingleApplication); if ( !isFrameComplete( dataSocket ) ) return; const QByteArray message = dataSocket->readAll(); writeAck( dataSocket ); ConnectionInfo &info = connectionMap[dataSocket]; info.stage = StageConnectedHeader; Q_EMIT q->receivedMessage( instanceId, message); } void SingleApplicationPrivate::slotClientConnectionClosed( QLocalSocket *closedSocket, quint32 instanceId ) { if( closedSocket->bytesAvailable() > 0 ) slotDataAvailable( closedSocket, instanceId ); } void SingleApplicationPrivate::randomSleep() { #if QT_VERSION >= QT_VERSION_CHECK( 5, 10, 0 ) QThread::msleep( QRandomGenerator::global()->bounded( 8u, 18u )); #else qsrand( QDateTime::currentMSecsSinceEpoch() % std::numeric_limits::max() ); QThread::msleep( qrand() % 11 + 8); #endif } void SingleApplicationPrivate::addAppData(const QString &data) { appDataList.push_back(data); } QStringList SingleApplicationPrivate::appData() const { return appDataList; } singleapplication-3.5.4+ds/singleapplication_p.h000066400000000000000000000074521507200001700220270ustar00rootroot00000000000000// 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. // // W A R N I N G !!! // ----------------- // // This file is not part of the SingleApplication API. It is used purely as an // implementation detail. This header file may change from version to // version without notice, or may even be removed. // #ifndef SINGLEAPPLICATION_P_H #define SINGLEAPPLICATION_P_H #include #include #include #include #include "singleapplication.h" struct InstancesInfo { bool primary; quint32 secondary; qint64 primaryPid; char primaryUser[128]; quint16 checksum; // Must be the last field }; struct ConnectionInfo { qint64 msgLen = 0; quint32 instanceId = 0; quint8 stage = 0; }; class SingleApplicationPrivate : public QObject { Q_OBJECT public: enum ConnectionType : quint8 { InvalidConnection = 0, NewInstance = 1, SecondaryInstance = 2, Reconnect = 3 }; enum ConnectionStage : quint8 { StageInitHeader = 0, StageInitBody = 1, StageConnectedHeader = 2, StageConnectedBody = 3, }; Q_DECLARE_PUBLIC(SingleApplication) SingleApplicationPrivate( SingleApplication *q_ptr ); ~SingleApplicationPrivate() override; static QString getUsername(); void genBlockServerName(); void initializeMemoryBlock() const; void startPrimary(); void startSecondary(); bool connectToPrimary( int msecs, ConnectionType connectionType ); quint16 blockChecksum() const; qint64 primaryPid() const; QString primaryUser() const; bool isFrameComplete(QLocalSocket *sock); void readMessageHeader(QLocalSocket *socket, ConnectionStage nextStage); void readInitMessageBody(QLocalSocket *socket); void writeAck(QLocalSocket *sock); bool writeConfirmedFrame(int msecs, const QByteArray &msg); bool writeConfirmedMessage(int msecs, const QByteArray &msg, SingleApplication::SendMode sendMode = SingleApplication::NonBlocking); static void randomSleep(); void addAppData(const QString &data); QStringList appData() const; SingleApplication *q_ptr; QSharedMemory *memory; QLocalSocket *socket; QLocalServer *server; quint32 instanceNumber; QString blockServerName; SingleApplication::Options options; QMap connectionMap; QStringList appDataList; public Q_SLOTS: void slotConnectionEstablished(); void slotDataAvailable( QLocalSocket*, quint32 ); void slotClientConnectionClosed( QLocalSocket*, quint32 ); }; #endif // SINGLEAPPLICATION_P_H