├── .github └── workflows │ └── build.yml ├── .gitignore ├── .qmake.conf ├── LICENSE ├── ProjectTemplate ├── AndroidManifest-service.part.xml ├── git.ignore ├── icon.png ├── icon@2x.png ├── main.cpp ├── project.pro ├── scinstall.bat.in ├── service.cpp ├── service.h ├── service.plist.in ├── service.service.in ├── service.socket └── wizard.json ├── README.md ├── deploy.json ├── doc ├── Doxyfile ├── backends.dox ├── doc.pro ├── doxme.py ├── gh_header.html ├── images │ └── GitHub_Logo.png ├── makedoc.sh ├── qtservice.dox ├── service.dox ├── servicebackend.dox ├── servicecontrol.dox ├── serviceplugin.dox └── terminal.dox ├── examples ├── examples.pro └── service │ ├── AndroidServiceTest │ ├── AndroidServiceTest.pro │ ├── android │ │ ├── AndroidManifest.xml │ │ ├── build.gradle │ │ ├── res │ │ │ └── values │ │ │ │ └── libs.xml │ │ └── src │ │ │ └── de │ │ │ └── skycoder42 │ │ │ └── qtservice │ │ │ └── test │ │ │ └── TestServiceHelper.java │ ├── controlhelper.cpp │ ├── controlhelper.h │ ├── main.cpp │ ├── main.qml │ ├── qml.qrc │ ├── testservice.cpp │ └── testservice.h │ ├── EchoControl │ ├── EchoControl.pro │ ├── controlwidget.cpp │ ├── controlwidget.h │ ├── controlwidget.ui │ └── main.cpp │ ├── EchoService │ ├── EchoService.pro │ ├── echoservice.cpp │ ├── echoservice.h │ ├── echoservice.plist.in │ ├── echoservice.service.in │ ├── echoservice.socket │ ├── main.cpp │ └── scinstall.bat.in │ ├── TerminalService │ ├── TerminalService.pro │ ├── main.cpp │ ├── terminalservice.cpp │ └── terminalservice.h │ └── service.pro ├── qtservice.pro ├── src ├── imports │ ├── imports.pro │ └── service │ │ ├── plugins.qmltypes │ │ ├── qmldir │ │ ├── qmlservicesingleton.cpp │ │ ├── qmlservicesingleton.h │ │ ├── qtservice_plugin.cpp │ │ ├── qtservice_plugin.h │ │ └── service.pro ├── java │ ├── java.pro │ └── src │ │ └── de │ │ └── skycoder42 │ │ └── qtservice │ │ └── AndroidService.java ├── plugins │ ├── plugins.pro │ └── servicebackends │ │ ├── android │ │ ├── android.json │ │ ├── android.pro │ │ ├── androidservicebackend.cpp │ │ ├── androidservicebackend.h │ │ ├── androidservicecontrol.cpp │ │ ├── androidservicecontrol.h │ │ ├── androidserviceplugin.cpp │ │ └── androidserviceplugin.h │ │ ├── launchd │ │ ├── launchd.json │ │ ├── launchd.pro │ │ ├── launchdservicebackend.cpp │ │ ├── launchdservicebackend.h │ │ ├── launchdservicecontrol.cpp │ │ ├── launchdservicecontrol.h │ │ ├── launchdserviceplugin.cpp │ │ └── launchdserviceplugin.h │ │ ├── servicebackends.pro │ │ ├── standard │ │ ├── standard.json │ │ ├── standard.pro │ │ ├── standardservicebackend.cpp │ │ ├── standardservicebackend.h │ │ ├── standardservicecontrol.cpp │ │ ├── standardservicecontrol.h │ │ ├── standardserviceplugin.cpp │ │ └── standardserviceplugin.h │ │ ├── systemd │ │ ├── de.skycoder42.QtService.ServicePlugin.systemd.xml │ │ ├── systemd.json │ │ ├── systemd.pro │ │ ├── systemdservicebackend.cpp │ │ ├── systemdservicebackend.h │ │ ├── systemdservicecontrol.cpp │ │ ├── systemdservicecontrol.h │ │ ├── systemdserviceplugin.cpp │ │ └── systemdserviceplugin.h │ │ └── windows │ │ ├── windows.json │ │ ├── windows.pro │ │ ├── windowsservicebackend.cpp │ │ ├── windowsservicebackend.h │ │ ├── windowsservicecontrol.cpp │ │ ├── windowsservicecontrol.h │ │ ├── windowsserviceplugin.cpp │ │ └── windowsserviceplugin.h ├── service │ ├── qtservice_global.h │ ├── qtservice_helpertypes.h │ ├── service.cpp │ ├── service.h │ ├── service.pro │ ├── service_p.h │ ├── servicebackend.cpp │ ├── servicebackend.h │ ├── servicebackend_p.h │ ├── servicecontrol.cpp │ ├── servicecontrol.h │ ├── servicecontrol_p.h │ ├── serviceplugin.cpp │ ├── serviceplugin.h │ ├── terminal.cpp │ ├── terminal.h │ ├── terminal_p.h │ ├── terminalclient.cpp │ ├── terminalclient_p.h │ ├── terminalserver.cpp │ └── terminalserver_p.h ├── src.pro └── translations │ ├── qtservice_de.ts │ ├── qtservice_template.ts │ └── translations.pro ├── sync.profile └── tests ├── auto ├── auto.pro ├── cmake │ ├── CMakeLists.txt │ └── cmake.pro ├── service │ ├── TestBaseLib │ │ ├── TestBaseLib.pro │ │ ├── basicservicetest.cpp │ │ └── basicservicetest.h │ ├── TestLaunchdService │ │ ├── TestLaunchdService.pro │ │ ├── de.skycoder42.qtservice.tests.testservice.plist │ │ └── tst_launchdservice.cpp │ ├── TestService │ │ ├── TestService.pro │ │ ├── main.cpp │ │ ├── testservice.cpp │ │ └── testservice.h │ ├── TestStandardService │ │ ├── TestStandardService.pro │ │ └── tst_standardservice.cpp │ ├── TestSystemdService │ │ ├── TestSystemdService.pro │ │ ├── testservice.service │ │ ├── testservice.socket │ │ └── tst_systemdservice.cpp │ ├── TestTerminalService │ │ ├── TestTerminalService.pro │ │ └── tst_terminalservice.cpp │ ├── TestWindowsService │ │ ├── TestWindowsService.pro │ │ └── tst_windowsservice.cpp │ ├── service.pro │ └── testlib.pri └── testrun.pri ├── global └── global.cfg └── tests.pro /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: CI build 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | strategy: 8 | fail-fast: false 9 | matrix: 10 | version: 11 | - 5.15.0 12 | platform: 13 | - gcc_64 14 | - android 15 | - wasm_32 16 | - msvc2019_64 17 | - msvc2019 18 | - winrt_x64_msvc2019 19 | - winrt_x86_msvc2019 20 | - winrt_armv7_msvc2019 21 | # - msvc2017_64 22 | # - msvc2017 23 | - mingw81_64 24 | - mingw81_32 25 | - clang_64 26 | - ios 27 | 28 | include: 29 | - platform: gcc_64 30 | os: ubuntu-latest 31 | - platform: android 32 | os: ubuntu-latest 33 | - platform: wasm_32 34 | os: ubuntu-latest 35 | emsdk: sdk-fastcomp-1.39.8-64bit 36 | - platform: msvc2019_64 37 | os: windows-latest 38 | - platform: msvc2019 39 | os: windows-latest 40 | - platform: winrt_x64_msvc2019 41 | os: windows-latest 42 | - platform: winrt_x86_msvc2019 43 | os: windows-latest 44 | - platform: winrt_armv7_msvc2019 45 | os: windows-latest 46 | # - platform: msvc2017_64 47 | # os: windows-latest 48 | # - platform: msvc2017 49 | # os: windows-latest 50 | - platform: mingw81_64 51 | os: windows-latest 52 | - platform: mingw81_32 53 | os: windows-latest 54 | - platform: clang_64 55 | os: macos-latest 56 | - platform: ios 57 | os: macos-latest 58 | 59 | runs-on: ${{matrix.os}} 60 | steps: 61 | - uses: actions/checkout@v1 62 | with: 63 | submodules: recursive 64 | - uses: actions/setup-python@v1 65 | - uses: mymindstorm/setup-emsdk@v5 66 | if: matrix.platform == 'wasm_32' 67 | with: 68 | version: ${{matrix.emsdk}} 69 | actions-cache-folder: emsdk-cache 70 | - uses: Skycoder42/action-setup-qt@master 71 | id: qt 72 | with: 73 | version: ${{matrix.version}} 74 | platform: ${{matrix.platform}} 75 | - name: Install systemd deps 76 | if: matrix.platform == 'gcc_64' 77 | run: | 78 | sudo apt-get -qq install libsystemd-dev dbus-user-session 79 | export XDG_RUNTIME_DIR=/run/user/`id -u` 80 | export XDG_SESSION_ID=2 81 | sudo mkdir -p $XDG_RUNTIME_DIR 82 | sudo chown `id -un` $XDG_RUNTIME_DIR 83 | sudo systemctl start user@`id -u`.service 84 | echo "::set-env name=XDG_RUNTIME_DIR::$XDG_RUNTIME_DIR" 85 | echo "::set-env name=XDG_SESSION_ID::$XDG_SESSION_ID" 86 | - name: qmake 87 | run: | 88 | qmake CONFIG+=install_ok QT_PLATFORM=${{matrix.platform}} 89 | ${{steps.qt.outputs.make}} qmake_all 90 | - name: make module 91 | run: | 92 | ${{steps.qt.outputs.make}} 93 | ${{steps.qt.outputs.make}} INSTALL_ROOT="${{steps.qt.outputs.installdir}}" install 94 | - name: make tests 95 | if: steps.qt.outputs.tests == 'true' && !contains(matrix.platform, 'mingw') 96 | run: | 97 | ${{steps.qt.outputs.make}} all 98 | ${{steps.qt.outputs.make}} ${{steps.qt.outputs.testflags}} run-tests 99 | - name: make examples 100 | if: matrix.platform == 'gcc_64' 101 | run: | 102 | ${{steps.qt.outputs.make}} sub-examples 103 | cd examples && ${{steps.qt.outputs.make}} INSTALL_ROOT="${{steps.qt.outputs.installdir}}" install 104 | - name: make doc 105 | if: matrix.platform == 'gcc_64' 106 | run: | 107 | ${{steps.qt.outputs.make}} doxygen 108 | cd doc && ${{steps.qt.outputs.make}} INSTALL_ROOT="${{steps.qt.outputs.installdir}}" install 109 | - name: upload examples to releases 110 | uses: Skycoder42/action-upload-release@master 111 | if: matrix.platform == 'gcc_64' && startsWith(github.ref, 'refs/tags/') 112 | with: 113 | repo_token: ${{secrets.GITHUB_TOKEN}} 114 | directory: ${{steps.qt.outputs.outdir}} 115 | platform: examples 116 | asset_name: qtservice-examples-${{matrix.version}} 117 | tag: ${{github.ref}} 118 | overwrite: true 119 | - name: upload doc to releases 120 | uses: Skycoder42/action-upload-release@master 121 | if: matrix.platform == 'gcc_64' && startsWith(github.ref, 'refs/tags/') 122 | with: 123 | repo_token: ${{secrets.GITHUB_TOKEN}} 124 | directory: ${{steps.qt.outputs.outdir}} 125 | platform: doc 126 | asset_name: qtservice-doc-${{matrix.version}} 127 | tag: ${{github.ref}} 128 | overwrite: true 129 | - name: upload module to releases 130 | uses: Skycoder42/action-upload-release@master 131 | if: startsWith(github.ref, 'refs/tags/') 132 | with: 133 | repo_token: ${{secrets.GITHUB_TOKEN}} 134 | directory: ${{steps.qt.outputs.outdir}} 135 | platform: ${{matrix.platform}} 136 | asset_name: qtservice-${{matrix.platform}}-${{matrix.version}} 137 | tag: ${{github.ref}} 138 | overwrite: true 139 | 140 | deploy: 141 | if: startsWith(github.ref, 'refs/tags/') 142 | needs: build 143 | runs-on: ubuntu-latest 144 | steps: 145 | - uses: actions/checkout@v1 146 | with: 147 | submodules: recursive 148 | path: source 149 | - uses: actions/setup-python@v1 150 | - uses: Skycoder42/action-deploy-qt@master 151 | with: 152 | token: ${{secrets.GITHUB_TOKEN}} 153 | version: 5.15.0 154 | host: ${{secrets.SSHFS_HOST}} 155 | key: ${{secrets.SSHFS_KEY}} 156 | port: ${{secrets.SSHFS_PORT}} 157 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # This file is used to ignore files which are generated 2 | # ---------------------------------------------------------------------------- 3 | 4 | *~ 5 | *.autosave 6 | *.a 7 | *.core 8 | *.moc 9 | *.o 10 | *.obj 11 | *.orig 12 | *.rej 13 | *.so 14 | *.so.* 15 | *_pch.h.cpp 16 | *_resource.rc 17 | *.qm 18 | .#* 19 | *.*# 20 | core 21 | !core/ 22 | tags 23 | .DS_Store 24 | .directory 25 | *.debug 26 | Makefile* 27 | *.prl 28 | *.app 29 | moc_*.cpp 30 | ui_*.h 31 | qrc_*.cpp 32 | Thumbs.db 33 | *.res 34 | *.rc 35 | /.qmake.cache 36 | /.qmake.stash 37 | build-* 38 | 39 | # qtcreator generated files 40 | *.pro.user* 41 | 42 | # xemacs temporary files 43 | *.flc 44 | 45 | # Vim temporary files 46 | .*.swp 47 | 48 | # Visual Studio generated files 49 | *.ib_pdb_index 50 | *.idb 51 | *.ilk 52 | *.pdb 53 | *.sln 54 | *.suo 55 | *.vcproj 56 | *vcproj.*.*.user 57 | *.ncb 58 | *.sdf 59 | *.opensdf 60 | *.vcxproj 61 | *vcxproj.* 62 | 63 | # MinGW generated files 64 | *.Debug 65 | *.Release 66 | 67 | # Python byte code 68 | *.pyc 69 | 70 | # Binaries 71 | # -------- 72 | *.dll 73 | *.exe 74 | 75 | # qpmx 76 | vendor 77 | qpmx.user.json 78 | qtservice_win.pro 79 | *.json.user 80 | -------------------------------------------------------------------------------- /.qmake.conf: -------------------------------------------------------------------------------- 1 | load(qt_build_config) 2 | 3 | CONFIG += warning_clean exceptions c++17 4 | win32:cross_compile: CONFIG += winrt 5 | DEFINES += QT_DEPRECATED_WARNINGS QT_ASCII_CAST_WARNINGS 6 | 7 | MODULE_VERSION_MAJOR = 2 8 | MODULE_VERSION_MINOR = 0 9 | MODULE_VERSION_PATCH = 2 10 | MODULE_VERSION_IMPORT = $${MODULE_VERSION_MAJOR}.$${MODULE_VERSION_MINOR} 11 | MODULE_VERSION = $${MODULE_VERSION_MAJOR}.$${MODULE_VERSION_MINOR}.$${MODULE_VERSION_PATCH} 12 | 13 | LOGGING_RULES=qt.service.*.debug=true 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2018, Felix Barz 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /ProjectTemplate/AndroidManifest-service.part.xml: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /ProjectTemplate/git.ignore: -------------------------------------------------------------------------------- 1 | # This file is used to ignore files which are generated 2 | # ---------------------------------------------------------------------------- 3 | 4 | *~ 5 | *.autosave 6 | *.a 7 | *.core 8 | *.moc 9 | *.o 10 | *.obj 11 | *.orig 12 | *.rej 13 | *.so 14 | *.so.* 15 | *_pch.h.cpp 16 | *_resource.rc 17 | *.qm 18 | .#* 19 | *.*# 20 | core 21 | !core/ 22 | tags 23 | .DS_Store 24 | .directory 25 | *.debug 26 | Makefile* 27 | *.prl 28 | *.app 29 | moc_*.cpp 30 | ui_*.h 31 | qrc_*.cpp 32 | Thumbs.db 33 | *.res 34 | *.rc 35 | /.qmake.cache 36 | /.qmake.stash 37 | 38 | # qtcreator generated files 39 | *.pro.user* 40 | 41 | # xemacs temporary files 42 | *.flc 43 | 44 | # Vim temporary files 45 | .*.swp 46 | 47 | # Visual Studio generated files 48 | *.ib_pdb_index 49 | *.idb 50 | *.ilk 51 | *.pdb 52 | *.sln 53 | *.suo 54 | *.vcproj 55 | *vcproj.*.*.user 56 | *.ncb 57 | *.sdf 58 | *.opensdf 59 | *.vcxproj 60 | *vcxproj.* 61 | 62 | # MinGW generated files 63 | *.Debug 64 | *.Release 65 | 66 | # Python byte code 67 | *.pyc 68 | 69 | # Binaries 70 | # -------- 71 | *.dll 72 | *.exe 73 | 74 | # qpmx 75 | .qpmx-dev-cache 76 | *.cppdummy 77 | -------------------------------------------------------------------------------- /ProjectTemplate/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skycoder42/QtService/b8f3862fda6c7d58bc21e03060ecee099668ab89/ProjectTemplate/icon.png -------------------------------------------------------------------------------- /ProjectTemplate/icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skycoder42/QtService/b8f3862fda6c7d58bc21e03060ecee099668ab89/ProjectTemplate/icon@2x.png -------------------------------------------------------------------------------- /ProjectTemplate/main.cpp: -------------------------------------------------------------------------------- 1 | #include "%{SvcHdrName}" 2 | 3 | int main(int argc, char *argv[]) 4 | { 5 | %{SvcCn} svc{argc, argv}; 6 | return svc.exec(); 7 | } 8 | -------------------------------------------------------------------------------- /ProjectTemplate/project.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = app 2 | 3 | QT = core service 4 | CONFIG += c++14 console 5 | CONFIG -= app_bundle 6 | 7 | TARGET = %{TargetName} 8 | VERSION = 1.0.0 9 | 10 | DEFINES += QT_DEPRECATED_WARNINGS QT_ASCII_CAST_WARNINGS QT_USE_QSTRINGBUILDER 11 | DEFINES += "TARGET=\\\\\\"$$TARGET\\\\\\"" 12 | DEFINES += "VERSION=\\\\\\"$$VERSION\\\\\\"" 13 | 14 | HEADERS += \\ 15 | %{SvcHdrName} 16 | 17 | SOURCES += \\ 18 | main.cpp \\ 19 | %{SvcSrcName} 20 | 21 | target.path = $$[QT_INSTALL_BINS] 22 | INSTALLS += target 23 | @if '%{CreateSystemd}' 24 | 25 | linux:!android { 26 | # install targets for systemd service files 27 | QMAKE_SUBSTITUTES += %{SvcSystemdName}.in 28 | 29 | install_svcconf.files += $$shadowed(%{SvcSystemdName}) 30 | @if '%{SocketPort}' 31 | install_svcconf.files += %{SvcSystemdSocketName} 32 | @endif 33 | install_svcconf.CONFIG += no_check_exist 34 | install_svcconf.path = $$[QT_INSTALL_LIBS]/systemd/system/ 35 | INSTALLS += install_svcconf 36 | } 37 | @endif 38 | @if '%{CreateWindows}' 39 | 40 | win32 { 41 | # install targets for windows service files 42 | QMAKE_SUBSTITUTES += %{SvcWindowsName}.in 43 | 44 | install_svcconf.files += $$shadowed(%{SvcWindowsName}) 45 | install_svcconf.CONFIG += no_check_exist 46 | install_svcconf.path = $$[QT_INSTALL_BINS] 47 | INSTALLS += install_svcconf 48 | } 49 | @endif 50 | @if '%{CreateLaunchd}' 51 | 52 | macos { 53 | # install targets for launchd service files 54 | QMAKE_SUBSTITUTES += %{SvcLaunchdName}.in 55 | 56 | install_svcconf.files += $$shadowed(%{SvcLaunchdName}) 57 | install_svcconf.CONFIG += no_check_exist 58 | install_svcconf.path = /Library/LaunchDaemons 59 | INSTALLS += install_svcconf 60 | } 61 | @endif 62 | @if '%{CreateAndroid}' 63 | 64 | OTHER_FILES += \\ 65 | AndroidManifest-service.part.xml 66 | @endif 67 | 68 | DISTFILES += $$QMAKE_SUBSTITUTES 69 | -------------------------------------------------------------------------------- /ProjectTemplate/scinstall.bat.in: -------------------------------------------------------------------------------- 1 | @echo off 2 | sc create %{ProjectName} binPath= \\"$$system_path($${target.path}\\\\$${TARGET}.exe) --backend windows\\" start= demand displayname= \\"%{ProjectName} Service\\" || exit /B 1 3 | sc description %{ProjectName} \\"The %{ProjectName} Service\\" || exit /B 1 4 | -------------------------------------------------------------------------------- /ProjectTemplate/service.cpp: -------------------------------------------------------------------------------- 1 | #include "%{SvcHdrName}" 2 | 3 | %{SvcCn}::%{SvcCn}(int &argc, char **argv) : 4 | Service{argc, argv} 5 | { 6 | QCoreApplication::setApplicationName(QStringLiteral(TARGET)); 7 | QCoreApplication::setApplicationVersion(QStringLiteral(VERSION)); 8 | //... 9 | } 10 | 11 | QtService::Service::CommandResult %{SvcCn}::onStart() 12 | { 13 | @if '%{SocketPort}' 14 | auto socket = getSocket(); 15 | @endif 16 | return CommandResult::Completed; 17 | } 18 | 19 | QtService::Service::CommandResult %{SvcCn}::onStop(int &exitCode) 20 | { 21 | exitCode = EXIT_SUCCESS; 22 | return CommandResult::Completed; 23 | } 24 | -------------------------------------------------------------------------------- /ProjectTemplate/service.h: -------------------------------------------------------------------------------- 1 | #ifndef %{SvcGuard} 2 | #define %{SvcGuard} 3 | 4 | #include 5 | 6 | class %{SvcCn} : public QtService::Service 7 | { 8 | Q_OBJECT 9 | 10 | public: 11 | explicit %{SvcCn}(int &argc, char **argv); 12 | 13 | protected: 14 | CommandResult onStart() override; 15 | CommandResult onStop(int &exitCode) override; 16 | }; 17 | 18 | #undef qService 19 | #define qService static_cast<%{SvcCn}*>(QtService::Service::instance()) 20 | 21 | #endif // %{SvcGuard} 22 | -------------------------------------------------------------------------------- /ProjectTemplate/service.plist.in: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Label 6 | %{SvcBundleName} 7 | ProgramArguments 8 | 9 | $${target.path}/$$TARGET 10 | --backend 11 | launchd 12 | 13 | RunAtLoad 14 | false 15 | @if '%{SocketPort}' 16 | Sockets 17 | 18 | Listeners 19 | 20 | SockServiceName 21 | %{SocketPort} 22 | 23 | 24 | @endif 25 | 26 | 27 | -------------------------------------------------------------------------------- /ProjectTemplate/service.service.in: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=%{ProjectName} Service 3 | Documentation=https://github.com/Skycoder42/QtService 4 | @if '%{SocketPort}' 5 | After=network-online.target %{SvcSystemdSocketName} 6 | @endif 7 | 8 | [Service] 9 | Type=notify 10 | NotifyAccess=exec 11 | ExecStart=$${target.path}/$$TARGET --backend systemd 12 | ExecReload=$${target.path}/$$TARGET --backend systemd reload 13 | ExecStop=$${target.path}/$$TARGET --backend systemd stop 14 | #WatchdogSec=10 15 | Restart=on-abnormal 16 | RuntimeDirectory=$$TARGET 17 | 18 | [Install] 19 | # Use the following for a system service 20 | #WantedBy=multi-user.target 21 | # Use the following for a user service 22 | WantedBy=default.target 23 | -------------------------------------------------------------------------------- /ProjectTemplate/service.socket: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=%{ProjectName} Service Socket 3 | Documentation=https://github.com/Skycoder42/QtService 4 | After=network.target 5 | PartOf=%{SvcSystemdName} 6 | 7 | [Socket] 8 | ListenStream=%{SocketPort} 9 | 10 | [Install] 11 | WantedBy=sockets.target 12 | -------------------------------------------------------------------------------- /ProjectTemplate/wizard.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "supportedProjectTypes": [ "Qt4ProjectManager.Qt4Project" ], 4 | "id": "H.QtService", 5 | "category": "F.Application", 6 | "trDescription": "Creates a QtService project with template service files for all supported platforms", 7 | "trDisplayName": "QtService Service Project", 8 | "trDisplayCategory": "Application", 9 | "icon": "icon.png", 10 | "featuresRequired": [ "QtSupport.Wizards.FeatureQt" ], 11 | "enabled": "%{JS: [ %{Plugins} ].indexOf('QmakeProjectManager') >= 0}", 12 | "options": 13 | [ 14 | { "key": "TargetName", "value": "%{JS: '%{ProjectName}'.toLowerCase()}" }, 15 | { "key": "ProFileName", "value": "%{JS: Util.fileName('%{ProjectDirectory}/%{ProjectName}', 'pro')}" }, 16 | 17 | { "key": "SvcCn", "value": "%{JS: Cpp.className('%{SvcName}')}" }, 18 | { "key": "SvcGuard", "value": "%{JS: Cpp.headerGuard('%{SvcHdrName}')}" }, 19 | 20 | { "key": "SvcSystemdName", "value": "%{JS: Util.fileName('%{JS: '%{SvcName}'.toLowerCase()}', 'service')}" }, 21 | { "key": "SvcSystemdSocketName", "value": "%{JS: Util.fileName('%{JS: '%{SvcName}'.toLowerCase()}', 'socket')}" }, 22 | { "key": "SvcWindowsName", "value": "%{JS: Util.fileName('%{JS: '%{SvcName}'.toLowerCase()}-install', 'bat')}" }, 23 | { "key": "SvcBundleName", "value": "%{BundlePrefix}.%{SvcName}" }, 24 | { "key": "SvcLaunchdName", "value": "%{JS: Util.fileName('%{JS: '%{SvcBundleName}'.toLowerCase()}.plist', 'plist')}" } 25 | ], 26 | 27 | "pages": 28 | [ 29 | { 30 | "trDisplayName": "Project Location", 31 | "trShortTitle": "Location", 32 | "typeId": "Project", 33 | "data": { "trDescription": "This wizard creates a service project with a service template and standard configuration files." } 34 | }, 35 | { 36 | "trDisplayName": "Select the service backends", 37 | "trShortTitle": "Backends", 38 | "typeId": "Fields", 39 | "data" : 40 | [ 41 | { 42 | "name": "CreateSystemd", 43 | "trDisplayName": "Create the systemd .service and .socket files", 44 | "type": "CheckBox", 45 | "data": 46 | { 47 | "checkedValue": "CreateSystemd", 48 | "uncheckedValue": "", 49 | "checked": "true" 50 | } 51 | }, 52 | { 53 | "name": "CreateWindows", 54 | "trDisplayName": "Create windows service install script", 55 | "type": "CheckBox", 56 | "data": 57 | { 58 | "checkedValue": "CreateWindows", 59 | "uncheckedValue": "", 60 | "checked": "true" 61 | } 62 | }, 63 | { 64 | "name": "CreateLaunchd", 65 | "trDisplayName": "Create launchd plist file", 66 | "type": "CheckBox", 67 | "data": 68 | { 69 | "checkedValue": "CreateLaunchd", 70 | "uncheckedValue": "", 71 | "checked": "true" 72 | } 73 | }, 74 | { 75 | "name": "CreateAndroid", 76 | "trDisplayName": "Create android manifest template", 77 | "type": "CheckBox", 78 | "data": 79 | { 80 | "checkedValue": "CreateAndroid", 81 | "uncheckedValue": "", 82 | "checked": "true" 83 | } 84 | }, 85 | { 86 | "name": "Sp1", 87 | "type": "Spacer", 88 | "data": { "factor": 2 } 89 | }, 90 | { 91 | "name": "SocketPort", 92 | "trDisplayName": "Socket-Activation port:", 93 | "type": "LineEdit", 94 | "mandatory": false, 95 | "data": { 96 | "trText": "", 97 | "validator": "^[0-9]*$" 98 | } 99 | }, 100 | { 101 | "name": "BundlePrefix", 102 | "trDisplayName": "Bundle prefix:", 103 | "type": "LineEdit", 104 | "data": { 105 | "trText": "com.example", 106 | "validator": "^[a-z][a-z0-9\-]*(?:\.[a-z0-9\-]+)*$", 107 | "enabled": "%{JS: %{CreateLaunchd} == 'CreateLaunchd'}" 108 | } 109 | } 110 | ] 111 | }, 112 | { 113 | "trDisplayName": "Define the primary service class", 114 | "trShortTitle": "Service", 115 | "typeId": "Fields", 116 | "data" : 117 | [ 118 | { 119 | "name": "SvcName", 120 | "trDisplayName": "Service class name:", 121 | "mandatory": true, 122 | "type": "LineEdit", 123 | "data": 124 | { 125 | "trText": "%{ProjectName}Service", 126 | "validator": "(?:(?:[a-zA-Z_][a-zA-Z_0-9]*::)*[a-zA-Z_][a-zA-Z_0-9]*|)" 127 | } 128 | }, 129 | { 130 | "name": "Sp2", 131 | "type": "Spacer" 132 | }, 133 | { 134 | "name": "SvcHdrName", 135 | "type": "LineEdit", 136 | "trDisplayName": "Header file:", 137 | "mandatory": true, 138 | "data": { "trText": "%{JS: Cpp.classToFileName('%{SvcName}', '%{JS: Util.preferredSuffix('text/x-c++hdr')}')}" } 139 | }, 140 | { 141 | "name": "SvcSrcName", 142 | "type": "LineEdit", 143 | "trDisplayName": "Source file:", 144 | "mandatory": true, 145 | "data": { "trText": "%{JS: Cpp.classToFileName('%{SvcName}', '%{JS: Util.preferredSuffix('text/x-c++src')}')}" } 146 | } 147 | ] 148 | }, 149 | { 150 | "trDisplayName": "Kit Selection", 151 | "trShortTitle": "Kits", 152 | "typeId": "Kits", 153 | "enabled": "%{IsTopLevelProject}", 154 | "data": 155 | { 156 | "projectFilePath": "%{ProFileName}", 157 | "enabled": "%{IsTopLevelProject}" 158 | } 159 | }, 160 | { 161 | "trDisplayName": "Project Management", 162 | "trShortTitle": "Summary", 163 | "typeId": "Summary" 164 | } 165 | ], 166 | "generators": 167 | [ 168 | { 169 | "typeId": "File", 170 | "data": 171 | [ 172 | { 173 | "source": "project.pro", 174 | "target": "%{ProFileName}", 175 | "openAsProject": true 176 | }, 177 | { 178 | "source": "main.cpp", 179 | "target": "%{ProjectDirectory}/main.cpp" 180 | }, 181 | { 182 | "source": "service.h", 183 | "target": "%{ProjectDirectory}/%{SvcHdrName}" 184 | }, 185 | { 186 | "source": "service.cpp", 187 | "target": "%{ProjectDirectory}/%{SvcSrcName}" 188 | }, 189 | { 190 | "source": "service.service.in", 191 | "target": "%{ProjectDirectory}/%{SvcSystemdName}.in", 192 | "condition": "%{JS: '%{CreateSystemd}' !== ''}" 193 | }, 194 | { 195 | "source": "service.socket", 196 | "target": "%{ProjectDirectory}/%{SvcSystemdSocketName}", 197 | "condition": "%{JS: '%{CreateSystemd}' !== '' && '%{SocketPort}' !== ''}" 198 | }, 199 | { 200 | "source": "scinstall.bat.in", 201 | "target": "%{ProjectDirectory}/%{SvcWindowsName}.in", 202 | "condition": "%{JS: '%{CreateWindows}' !== ''}" 203 | }, 204 | { 205 | "source": "service.plist.in", 206 | "target": "%{ProjectDirectory}/%{SvcLaunchdName}.in", 207 | "condition": "%{JS: '%{CreateLaunchd}' !== ''}" 208 | }, 209 | { 210 | "source": "AndroidManifest-service.part.xml", 211 | "target": "%{ProjectDirectory}/android/AndroidManifest-service.part.xml", 212 | "condition": "%{JS: '%{CreateAndroid}' !== ''}" 213 | }, 214 | { 215 | "source": "git.ignore", 216 | "target": "%{ProjectDirectory}/.gitignore", 217 | "condition": "%{JS: !%{IsSubproject} && '%{VersionControl}' === 'G.Git'}" 218 | } 219 | ] 220 | } 221 | ] 222 | } 223 | -------------------------------------------------------------------------------- /deploy.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "QtService", 3 | "description": "A platform independent library to easily create system services and use some of their features.", 4 | "modules": [ "QtService" ], 5 | "dependencies": [], 6 | "license": { 7 | "name": "BSD-3-Clause", 8 | "path": "LICENSE" 9 | }, 10 | "installs": { 11 | "ProjectTemplate": "Tools/QtCreator/share/qtcreator/templates/wizards/projects/qtservice" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /doc/doc.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = aux 2 | 3 | OTHER_FILES += Doxyfile \ 4 | makedoc.sh \ 5 | doxme.py \ 6 | ../README.md \ 7 | *.dox \ 8 | snippets/*.cpp \ 9 | images/* \ 10 | gh_header.html 11 | 12 | mkpath($$OUT_PWD/qtservice) 13 | !exists($$OUT_PWD/qtservice.qch):write_file($$OUT_PWD/qtservice.qch, __NOTHING) 14 | 15 | docTarget.target = doxygen 16 | docTarget.commands = $$PWD/makedoc.sh "$$PWD" "$$MODULE_VERSION" "$$[QT_INSTALL_BINS]" "$$[QT_INSTALL_HEADERS]" "$$[QT_INSTALL_DOCS]" 17 | QMAKE_EXTRA_TARGETS += docTarget 18 | 19 | docInst1.path = $$[QT_INSTALL_DOCS] 20 | docInst1.files = $$OUT_PWD/qtservice.qch 21 | docInst1.CONFIG += no_check_exist 22 | docInst2.path = $$[QT_INSTALL_DOCS] 23 | docInst2.files = $$OUT_PWD/qtservice 24 | INSTALLS += docInst1 docInst2 25 | 26 | DISTFILES += \ 27 | serviceplugin.dox 28 | -------------------------------------------------------------------------------- /doc/doxme.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # $1 The readme to be transformed 3 | # $pwd: dest dir 4 | 5 | import sys 6 | 7 | def readFirst(line, out): 8 | if line[0:2] != "# ": 9 | raise ValueError("Expected first line to start with '# '") 10 | # skip the first line 11 | out.write("[TOC]\n\n") 12 | 13 | readCounter = 0 14 | def readMore(line, out): 15 | global readCounter 16 | if line[0:2] == "##": 17 | out.write(line[1:] + " {{#qtservice_readme_label_{}}}\n".format(readCounter)) 18 | readCounter += 1 19 | else: 20 | out.write(line + "\n") 21 | 22 | #read args 23 | readme = sys.argv[1] 24 | doxme = "./README.md" 25 | 26 | inFile = open(readme, "r") 27 | outFile = open(doxme, "w") 28 | 29 | isFirst = True 30 | for line in inFile: 31 | if isFirst: 32 | readFirst(line[:-1], outFile) 33 | isFirst = False 34 | else: 35 | readMore(line[:-1], outFile) 36 | 37 | inFile.close(); 38 | outFile.close(); 39 | -------------------------------------------------------------------------------- /doc/gh_header.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | $projectname: $title 10 | $title 11 | 12 | 13 | 14 | $treeview 15 | $search 16 | $mathjax 17 | 18 | $extrastylesheet 19 | 20 | 21 |
22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 38 | 39 | 40 | 41 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 |
33 |
$projectname 34 |  $projectnumber 35 |
36 |
$projectbrief
37 |
42 |
$projectbrief
43 |
$searchbox
54 | 55 | 56 | 57 |
58 | 59 | 60 | -------------------------------------------------------------------------------- /doc/images/GitHub_Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Skycoder42/QtService/b8f3862fda6c7d58bc21e03060ecee099668ab89/doc/images/GitHub_Logo.png -------------------------------------------------------------------------------- /doc/makedoc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # $1: $$SRCDIR 3 | # $2: $$VERSION 4 | # $3: $$[QT_INSTALL_BINS] 5 | # $4: $$[QT_INSTALL_HEADERS] 6 | # $5: $$[QT_INSTALL_DOCS] 7 | # $pwd: dest dir 8 | set -e 9 | 10 | scriptDir=$(dirname "$0") 11 | destDir="$(pwd)" 12 | srcDir=$1 13 | version=$2 14 | verTag=$(echo "$version" | sed -e 's/\.//g') 15 | qtBins=$3 16 | qtHeaders=$4 17 | qtDocs=$5 18 | doxyTemplate="$srcDir/Doxyfile" 19 | doxyRes=Doxyfile.generated 20 | readme="$destDir/README.md" 21 | doxme="$scriptDir/doxme.py" 22 | 23 | python3 "$doxme" "$srcDir/../README.md" 24 | 25 | cat "$doxyTemplate" > $doxyRes 26 | echo "PROJECT_NUMBER = \"$version\"" >> $doxyRes 27 | echo "INPUT += \"$readme\"" >> $doxyRes 28 | echo "USE_MDFILE_AS_MAINPAGE = \"$readme\"" >> $doxyRes 29 | echo "OUTPUT_DIRECTORY = \"$destDir\"" >> $doxyRes 30 | echo "QHP_NAMESPACE = \"de.skycoder42.qtservice.$verTag\"" >> $doxyRes 31 | echo "QHP_CUST_FILTER_NAME = \"Service $version\"" >> $doxyRes 32 | echo "QHP_CUST_FILTER_ATTRS = \"qtservice $version\"" >> $doxyRes 33 | echo "QHG_LOCATION = \"$qtBins/qhelpgenerator\"" >> $doxyRes 34 | echo "INCLUDE_PATH += \"$qtHeaders\"" >> $doxyRes 35 | echo "GENERATE_TAGFILE = \"$destDir/qtservice/qtservice.tags\"" >> $doxyRes 36 | if [ "$DOXY_STYLE" ]; then 37 | echo "HTML_STYLESHEET = \"$DOXY_STYLE\"" >> $doxyRes 38 | fi 39 | if [ "$DOXY_STYLE_EXTRA" ]; then 40 | echo "HTML_EXTRA_STYLESHEET = \"$DOXY_STYLE_EXTRA\"" >> $doxyRes 41 | fi 42 | 43 | for tagFile in $(find "$qtDocs" -name *.tags); do 44 | if [ $(basename "$tagFile") != "qtservice.tags" ]; then 45 | echo "TAGFILES += \"$tagFile=https://doc.qt.io/qt-5\"" >> $doxyRes 46 | fi 47 | done 48 | 49 | cd "$srcDir" 50 | doxygen "$destDir/$doxyRes" 51 | -------------------------------------------------------------------------------- /doc/qtservice.dox: -------------------------------------------------------------------------------- 1 | /*! 2 | @namespace de::skycoder42::QtService 3 | @brief The QML import for the QtService QML module 4 | 5 | Current Version
6 |         1.1 7 | 8 | Available Types 9 | - QtService (singleton) 10 | - @ref QtService::Service "Service" (uncreatable) 11 | - @ref QtService::ServiceControl "ServiceControl" (uncreatable) 12 | - @ref QtService::Terminal "Terminal" (uncreatable) 13 | */ 14 | -------------------------------------------------------------------------------- /doc/servicebackend.dox: -------------------------------------------------------------------------------- 1 | /*! 2 | @class QtService::ServiceBackend 3 | 4 | This class is only needed when implementing your own service backend. It's what the Service uses 5 | internally to properly run and interact with the service systems 6 | 7 | @sa Service, ServicePlugin 8 | */ 9 | 10 | /*! 11 | @fn QtService::ServiceBackend::runService 12 | 13 | @param argc The argc from Service, i.e. from the main 14 | @param argv The argv from Service, i.e. from the main 15 | @param flags The flags from Service, i.e. from the main 16 | @returns The return value from QCoreApplication::exec 17 | 18 | This method should perform all the required startup stuff for the service, prepare logging etc. What 19 | must do is to create a QCoreApplication (or another application) and eventually run 20 | QCoreApplication::exec - as this method should also block until execution finished. You should return 21 | the exit code form the main if possible. 22 | 23 | Returning from this method early should typically be done only in case of an error and an error exit 24 | code returned. 25 | 26 | Internally, you must use ServiceBackend::processServiceCommand (and 27 | ServiceBackend::processServiceCallback) at the appropriate points in your implementation to call the 28 | corresponding service methods. 29 | 30 | @sa Service::exec, Service::Service 31 | */ 32 | 33 | /*! 34 | @fn QtService::ServiceBackend::quitService 35 | 36 | Your implementation should perform a graceful service exit. This means a call to this method should 37 | behave the same as stopping the service from the service manager. 38 | 39 | @sa Service::quit, Service::onStop 40 | */ 41 | 42 | /*! 43 | @fn QtService::ServiceBackend::reloadService 44 | 45 | Your implementation should perform a service reload. This means a call to this method should behave 46 | the same as reloading the service from the service manager. If reloading is not supported by your 47 | backend, simply call `processServiceCommand(ReloadCommand);` to properly handle the reload with 48 | doing anything service-manager related. 49 | 50 | @sa Service::reload, Service::onReload 51 | */ 52 | 53 | /*! 54 | @fn QtService::ServiceBackend::getActivatedSockets 55 | 56 | @param name The name of the socket(s) to be retrieved, or a null name for the default socket. 57 | @returns A list with all sockets found for that name, or an empty list if non were found 58 | 59 | If your service backends supports socket activation, implement this method to make the available. 60 | It is either called with a name passed to Service::getSockets, or with a null bytearray if the 61 | default socket (Service::getSocket) was requested. Sockets returned from this method should be ready 62 | to use, aka in the listening state (this is typically the case for bound sockets) 63 | 64 | @sa Service::getSockets, Service::getSocket, QByteArray::isNull 65 | */ 66 | 67 | /*! 68 | @fn QtService::ServiceBackend::signalTriggered 69 | 70 | @param signal The signal that was triggered 71 | 72 | If you need to handle signals, this is the easiest way. Please note that this method is called 73 | asynchronously. So if you need signal handlers that are synchronous, you must implement them 74 | yourself. The signal values are the standard defined values of signals for all platforms, i.e. the 75 | SIGINT signal on unix is the same as that define from the signal.h header. 76 | 77 | In order to be able to catch signals with this method they must be registered first. Use 78 | ServiceBackend::registerForSignal to do so. 79 | 80 | @sa ServiceBackend::registerForSignal, ServiceBackend::unregisterFromSignal 81 | */ 82 | 83 | /*! 84 | @fn QtService::ServiceBackend::processServiceCommand 85 | 86 | @param code The command code to be executed 87 | 88 | This method calls the correspondig onCommand method of the Service instance. This method takes care 89 | of both sync and async calls. For usage, you should always assume asynchronous runs. In other words, 90 | do not complete the operation after this method returns. Instead connect to the coresponding done 91 | signal and continue from there. 92 | 93 | @note Connections to those signals should be done before calling this method, as they might be 94 | emitted before the method returns! Use queued connections if that would conflict with your 95 | implementation 96 | 97 | The methods called and signals to connect to for each command are: 98 | Command | Method | Signal 99 | --------------------------------|-------------------|-------- 100 | ServiceBackend::StartCommand | Service::onStart | Service::started 101 | ServiceBackend::StopCommand | Service::onStop | Service::stopped 102 | ServiceBackend::ReloadCommand | Service::onReload | Service::reloaded 103 | ServiceBackend::PauseCommand | Service::onPause | Service::paused 104 | ServiceBackend::ResumeCommand | Service::onResume | Service::resumed 105 | 106 | @sa ServiceBackend::ServiceCommand, ServiceBackend::service 107 | */ 108 | 109 | /*! 110 | @fn QtService::ServiceBackend::processServiceCallbackImpl 111 | 112 | @param kind The kind of callback to be called 113 | @param args The arguments of the callback 114 | @returns The return of the callback 115 | 116 | Wrapper that calls Service::onCallback and returns it's value. This method call is, unlike other 117 | commands, synchronous. 118 | 119 | @sa ServiceBackend::processServiceCallback, Service::onCallback 120 | */ 121 | 122 | /*! 123 | @fn QtService::ServiceBackend::registerForSignal 124 | 125 | @param signal The signal to register for 126 | @returns true if the signal was (or already has been) registered 127 | 128 | You need to call this method to register a signal you want to handle. After registering it, it will 129 | be delivered via ServiceBackend::signalTriggered as soon as it is triggered. 130 | 131 | @sa ServiceBackend::signalTriggered, ServiceBackend::unregisterFromSignal 132 | */ 133 | 134 | /*! 135 | @fn QtService::ServiceBackend::unregisterFromSignal 136 | 137 | @param signal The signal to unregister from 138 | @returns true if the signal was (or already has been) unregistered 139 | 140 | After unregistering, the "default signal handler" will be used again to handle such a signal. In 141 | other words the state before the registration is recovered. 142 | 143 | @sa ServiceBackend::signalTriggered, ServiceBackend::registerForSignal 144 | */ 145 | 146 | /*! 147 | @fn QtService::ServiceBackend::preStartService 148 | 149 | @returns true if startup can be continued, false to cancle early 150 | 151 | You should call this method right after creating the QCoreApplication object and after setting up 152 | logging. It's a wrapper that calls Service::preStart and returns it's value. If false is returned 153 | the service cannot start properly and you should exit as soon as possible. 154 | 155 | @note If you need to complete the startup and enter the running state, do so. But if false was 156 | returned instead of calling the start command simply quit the service without ever starting. If you 157 | implement it that way, make shure neither the start nor the stop command are ever triggered 158 | 159 | @sa Service::preStart 160 | */ 161 | 162 | -------------------------------------------------------------------------------- /doc/serviceplugin.dox: -------------------------------------------------------------------------------- 1 | /*! 2 | @class QtService::ServicePlugin 3 | 4 | The plugin that must be implemented to create a Service plugin. The plugins IID is 5 | #QtService_ServicePlugin_Iid. The plugin most have a json file with the following layout: 6 | 7 | @code{.json} 8 | { 9 | "Keys" : [ "backend1", "backend2", ... ] 10 | } 11 | @endcode 12 | 13 | Each string in the `Keys` array is the name of a kind of backend that is provided from that 14 | plugin. Most plugins provide only one type of backend, so for most cases you have only 1 15 | element in the list. Please note that the plugin must be able to handle all these providers 16 | when passed to it's methods. 17 | 18 | For more details on how to implement such a plugin, have a look at: 19 | [The High-Level API: Writing Qt Extensions](https://doc.qt.io/qt-5/plugins-howto.html#the-high-level-api-writing-qt-extensions) 20 | 21 | @sa ServiceBackend, ServiceControl, #QtService_ServicePlugin_Iid 22 | */ 23 | 24 | /*! 25 | @fn QtService::ServicePlugin::findServiceId 26 | 27 | @param backend The service manager backend to create a service for 28 | @param serviceName The name of the service 29 | @param domain The domain of the service 30 | @returns A serviceId derived from the given name and possibly domain 31 | 32 | This method is internally used to resolve serviceIDs when only names are given. You implementation 33 | should basically do the opposite of the ServiceControl::serviceName method. 34 | 35 | @sa ServiceControl::createFromName 36 | */ 37 | 38 | /*! 39 | @fn QtService::ServicePlugin::createServiceBackend 40 | 41 | @param backend The service manager backend to create a service for 42 | @param service The service instance that will use the backend to run as a service 43 | @returns A newly created backend instance for the given backend. 44 | 45 | This method is only called from the service instance to create the backend used to run the 46 | service. This method is called before any Qt-related stuff has been done - so there is not 47 | QCoreApplication yet. 48 | 49 | @sa Service::exec, ServiceBackend 50 | */ 51 | 52 | /*! 53 | @fn QtService::ServicePlugin::createServiceControl 54 | 55 | @param backend The service manager backend to create a control for 56 | @param serviceId The identifier of the service to create the control for 57 | @param parent The parent for the control. Should be set as the QObject::parent of the newly 58 | created control 59 | @returns A newly created control instance for the given backend. 60 | 61 | This method must be threadsafe 62 | 63 | You should always return a valid backend for all valid providers and don't need to perform 64 | additional validity checks. It is save to return `nullptr` in case the store cannot be created. 65 | The backends passed to this method are the ones defined in the json file. 66 | 67 | @attention You should always use detectNamedService() in this method to be able to handle controls 68 | that are created via ServiceControl::createFromName instead of a service id. 69 | 70 | @sa ServiceControl::create, ServiceControl::createFromName, ServiceControl, 71 | ServicePlugin::detectNamedService 72 | */ 73 | -------------------------------------------------------------------------------- /examples/examples.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = subdirs 2 | 3 | SUBDIRS = service 4 | -------------------------------------------------------------------------------- /examples/service/AndroidServiceTest/AndroidServiceTest.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = app 2 | 3 | QT += quick service androidextras 4 | CONFIG += c++14 5 | 6 | HEADERS += \ 7 | testservice.h \ 8 | controlhelper.h 9 | 10 | SOURCES += \ 11 | main.cpp \ 12 | testservice.cpp \ 13 | controlhelper.cpp 14 | 15 | RESOURCES += qml.qrc 16 | 17 | DISTFILES += \ 18 | android/AndroidManifest.xml \ 19 | android/res/values/libs.xml \ 20 | android/build.gradle \ 21 | android/src/de/skycoder42/qtservice/test/TestServiceHelper.java 22 | 23 | ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android 24 | 25 | target.path = $$[QT_INSTALL_EXAMPLES]/service/$$TARGET 26 | !install_ok: INSTALLS += target 27 | -------------------------------------------------------------------------------- /examples/service/AndroidServiceTest/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | google() 4 | jcenter() 5 | } 6 | 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:3.2.0' 9 | } 10 | } 11 | 12 | repositories { 13 | google() 14 | jcenter() 15 | } 16 | 17 | apply plugin: 'com.android.application' 18 | 19 | dependencies { 20 | implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar']) 21 | } 22 | 23 | android { 24 | /******************************************************* 25 | * The following variables: 26 | * - androidBuildToolsVersion, 27 | * - androidCompileSdkVersion 28 | * - qt5AndroidDir - holds the path to qt android files 29 | * needed to build any Qt application 30 | * on Android. 31 | * 32 | * are defined in gradle.properties file. This file is 33 | * updated by QtCreator and androiddeployqt tools. 34 | * Changing them manually might break the compilation! 35 | *******************************************************/ 36 | 37 | compileSdkVersion androidCompileSdkVersion.toInteger() 38 | 39 | buildToolsVersion androidBuildToolsVersion 40 | 41 | sourceSets { 42 | main { 43 | manifest.srcFile 'AndroidManifest.xml' 44 | java.srcDirs = [qt5AndroidDir + '/src', 'src', 'java'] 45 | aidl.srcDirs = [qt5AndroidDir + '/src', 'src', 'aidl'] 46 | res.srcDirs = [qt5AndroidDir + '/res', 'res'] 47 | resources.srcDirs = ['src'] 48 | renderscript.srcDirs = ['src'] 49 | assets.srcDirs = ['assets'] 50 | jniLibs.srcDirs = ['libs'] 51 | } 52 | } 53 | 54 | lintOptions { 55 | abortOnError false 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /examples/service/AndroidServiceTest/android/res/values/libs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | https://download.qt.io/ministro/android/qt5/qt-5.9 5 | 6 | 7 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /examples/service/AndroidServiceTest/android/src/de/skycoder42/qtservice/test/TestServiceHelper.java: -------------------------------------------------------------------------------- 1 | package de.skycoder42.qtservice.test; 2 | 3 | import android.content.Context; 4 | import android.app.Service; 5 | import android.app.Notification; 6 | import android.app.NotificationChannel; 7 | import android.app.NotificationManager; 8 | 9 | class TestServiceHelper 10 | { 11 | private static final int NotId = 42; 12 | private static final String ChannelId = "42"; 13 | 14 | public static void registerChannel(Context context) 15 | { 16 | NotificationChannel foreground = new NotificationChannel(ChannelId, 17 | "Test Service", 18 | NotificationManager.IMPORTANCE_MIN); 19 | foreground.setDescription("Test Service"); 20 | foreground.enableLights(false); 21 | foreground.enableVibration(false); 22 | foreground.setShowBadge(false); 23 | 24 | NotificationManager manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); 25 | manager.createNotificationChannel(foreground); 26 | } 27 | 28 | public static void notifyRunning(Service context, String message) 29 | { 30 | Notification.Builder builder = new Notification.Builder(context, ChannelId) 31 | .setContentTitle("Test Service") 32 | .setContentText(message) 33 | .setSmallIcon(android.R.drawable.ic_media_play) 34 | .setOngoing(true); 35 | 36 | context.startForeground(NotId, builder.build()); 37 | } 38 | 39 | public static void updateNotifyRunning(Service context, String message) 40 | { 41 | Notification.Builder builder = new Notification.Builder(context, ChannelId) 42 | .setContentTitle("Test Service") 43 | .setContentText(message) 44 | .setSmallIcon(android.R.drawable.ic_media_ff) 45 | .setOngoing(true); 46 | 47 | NotificationManager manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); 48 | manager.notify(NotId, builder.build()); 49 | } 50 | 51 | public static void stopNotifyRunning(Service context) 52 | { 53 | context.stopForeground(true); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /examples/service/AndroidServiceTest/controlhelper.cpp: -------------------------------------------------------------------------------- 1 | #include "controlhelper.h" 2 | #include 3 | #include 4 | 5 | Q_DECLARE_METATYPE(QAndroidIntent) 6 | Q_DECLARE_METATYPE(QAndroidServiceConnection*) 7 | Q_DECLARE_METATYPE(QtAndroid::BindFlags) 8 | 9 | ControlHelper::ControlHelper(QtService::ServiceControl *parent) : 10 | QObject(parent), 11 | _control(parent) 12 | {} 13 | 14 | void ControlHelper::startIntent(const QString &action) 15 | { 16 | _control->callCommand("startWithIntent", QAndroidIntent{action}); 17 | } 18 | 19 | void ControlHelper::bind() 20 | { 21 | if(_connection) 22 | return; 23 | 24 | _connection = new Connection(); 25 | _control->callCommand("bind", 26 | static_cast(_connection), 27 | QtAndroid::BindFlags{QtAndroid::BindFlag::AutoCreate}); 28 | } 29 | 30 | void ControlHelper::unbind() 31 | { 32 | if(!_connection) 33 | return; 34 | _control->callCommand("unbind", static_cast(_connection)); 35 | _connection = nullptr; 36 | } 37 | 38 | void ControlHelper::Connection::onServiceConnected(const QString &name, const QAndroidBinder &serviceBinder) 39 | { 40 | Q_UNUSED(serviceBinder) 41 | toast(QStringLiteral("Service bound: ") + name); 42 | } 43 | 44 | void ControlHelper::Connection::onServiceDisconnected(const QString &name) 45 | { 46 | toast(QStringLiteral("Service unbound: ") + name); 47 | } 48 | 49 | void ControlHelper::Connection::toast(const QString &message) 50 | { 51 | QtAndroid::runOnAndroidThread([message](){ 52 | auto toast = QAndroidJniObject::callStaticObjectMethod("android/widget/Toast", 53 | "makeText", "(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;", 54 | QtAndroid::androidContext().object(), 55 | QAndroidJniObject::fromString(message).object(), 56 | 1); 57 | toast.callMethod("show"); 58 | }); 59 | } 60 | -------------------------------------------------------------------------------- /examples/service/AndroidServiceTest/controlhelper.h: -------------------------------------------------------------------------------- 1 | #ifndef CONTROLHELPER_H 2 | #define CONTROLHELPER_H 3 | 4 | #include 5 | #include 6 | 7 | class ControlHelper : public QObject 8 | { 9 | Q_OBJECT 10 | 11 | public: 12 | explicit ControlHelper(QtService::ServiceControl *parent = nullptr); 13 | 14 | public slots: 15 | void startIntent(const QString &action); 16 | void bind(); 17 | void unbind(); 18 | 19 | private: 20 | class Connection : public QAndroidServiceConnection 21 | { 22 | public: 23 | void onServiceConnected(const QString &name, const QAndroidBinder &serviceBinder) override; 24 | void onServiceDisconnected(const QString &name) override; 25 | 26 | private: 27 | void toast(const QString &message); 28 | } *_connection = nullptr; 29 | 30 | QtService::ServiceControl *_control; 31 | }; 32 | 33 | #endif // CONTROLHELPER_H 34 | -------------------------------------------------------------------------------- /examples/service/AndroidServiceTest/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "testservice.h" 7 | #include "controlhelper.h" 8 | 9 | namespace { 10 | 11 | int serviceMain(int argc, char *argv[]) 12 | { 13 | TestService service{argc, argv}; 14 | return service.exec(); 15 | } 16 | 17 | int activityMain(int argc, char *argv[]) 18 | { 19 | QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); 20 | QGuiApplication app(argc, argv); 21 | 22 | QAndroidJniObject::callStaticMethod("de/skycoder42/qtservice/test/TestServiceHelper", 23 | "registerChannel", "(Landroid/content/Context;)V", 24 | QtAndroid::androidContext().object()); 25 | 26 | QQmlApplicationEngine engine; 27 | auto control = QtService::ServiceControl::create(QStringLiteral("android"), QStringLiteral("de.skycoder42.qtservice.AndroidService"), &engine); 28 | auto helper = new ControlHelper{control}; 29 | engine.rootContext()->setContextProperty(QStringLiteral("control"), control); 30 | engine.rootContext()->setContextProperty(QStringLiteral("helper"), helper); 31 | engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); 32 | if (engine.rootObjects().isEmpty()) 33 | return -1; 34 | 35 | return app.exec(); 36 | } 37 | 38 | } 39 | 40 | int main(int argc, char *argv[]) 41 | { 42 | for(auto i = 0; i < argc; i++) { 43 | if(qstrcmp(argv[i], "--backend") == 0) 44 | return serviceMain(argc, argv); 45 | } 46 | return activityMain(argc, argv); 47 | } 48 | -------------------------------------------------------------------------------- /examples/service/AndroidServiceTest/main.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.11 2 | import QtQuick.Layouts 1.11 3 | import QtQuick.Window 2.11 4 | import QtQuick.Controls 2.4 5 | import de.skycoder42.QtService 2.0 6 | 7 | Window { 8 | visible: true 9 | width: 640 10 | height: 480 11 | title: qsTr("Hello World") 12 | 13 | Connections { 14 | target: control 15 | onErrorChanged: { 16 | console.log(error); 17 | errorDialog.open(); 18 | } 19 | } 20 | 21 | Dialog { 22 | id: errorDialog 23 | title: "Service error" 24 | standardButtons: Dialog.Ok 25 | visible: false 26 | Label { 27 | anchors.fill: parent 28 | text: control.error 29 | } 30 | } 31 | 32 | GridLayout { 33 | anchors.fill: parent 34 | columns: 2 35 | 36 | Label { 37 | Layout.columnSpan: 2 38 | Layout.fillWidth: true 39 | text: "Service valid: " + control.serviceExists() 40 | } 41 | 42 | Button { 43 | text: "Start Service" 44 | Layout.fillWidth: true 45 | onClicked: control.start(); 46 | } 47 | 48 | Button { 49 | text: "Stop Service" 50 | Layout.fillWidth: true 51 | onClicked: control.stop(); 52 | } 53 | 54 | Button { 55 | text: "Bind Service" 56 | Layout.fillWidth: true 57 | onClicked: helper.bind(); 58 | } 59 | 60 | Button { 61 | text: "Unbind Service" 62 | Layout.fillWidth: true 63 | onClicked: helper.unbind(); 64 | } 65 | 66 | Button { 67 | text: "Start with intent" 68 | Layout.fillWidth: true 69 | onClicked: helper.startIntent(actionField.text) 70 | } 71 | 72 | TextField { 73 | id: actionField 74 | placeholderText: "start action text" 75 | Layout.fillWidth: true 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /examples/service/AndroidServiceTest/qml.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | main.qml 4 | 5 | 6 | -------------------------------------------------------------------------------- /examples/service/AndroidServiceTest/testservice.cpp: -------------------------------------------------------------------------------- 1 | #include "testservice.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace { 9 | 10 | QAtomicInt wasStarted = 0; 11 | 12 | } 13 | 14 | TestService::TestService(int &argc, char **argv) : 15 | Service{argc, argv} 16 | { 17 | addCallback("onStartCommand", &TestService::onStartCommand); 18 | addCallback("onBind", &TestService::onBind); 19 | } 20 | 21 | QtService::Service::CommandResult TestService::onStart() 22 | { 23 | qDebug() << "onStart"; 24 | doStartNotify(); 25 | //QTimer::singleShot(5000, this, &TestService::quit); 26 | return OperationCompleted; 27 | } 28 | 29 | QtService::Service::CommandResult TestService::onStop(int &) 30 | { 31 | qDebug() << "onStop"; 32 | QAndroidJniObject::callStaticMethod("de/skycoder42/qtservice/test/TestServiceHelper", 33 | "stopNotifyRunning", "(Landroid/app/Service;)V", 34 | QtAndroid::androidService().object()); 35 | // QTimer::singleShot(3000, this, [this](){ 36 | // emit stopped(); 37 | // }); 38 | // return OperationPending; 39 | return OperationCompleted; 40 | } 41 | 42 | int TestService::onStartCommand(const QAndroidIntent &intent, int flags, int startId) 43 | { 44 | qDebug() << "onStartCommand" << intent.handle().toString() << flags << startId; 45 | doStartNotify(); 46 | auto action = intent.handle().callObjectMethod("getAction", "()Ljava/lang/String;").toString(); 47 | if(!action.isEmpty()) { 48 | QAndroidJniObject::callStaticMethod("de/skycoder42/qtservice/test/TestServiceHelper", 49 | "updateNotifyRunning", "(Landroid/app/Service;Ljava/lang/String;)V", 50 | QtAndroid::androidService().object(), 51 | QAndroidJniObject::fromString(QStringLiteral("Service intent with action: ") + action).object()); 52 | } 53 | return 1; // START_STICKY 54 | } 55 | 56 | QAndroidBinder *TestService::onBind(const QAndroidIntent &intent) 57 | { 58 | qDebug() << "onBind" << intent.handle().toString(); 59 | return new QAndroidBinder{}; 60 | } 61 | 62 | void TestService::doStartNotify() 63 | { 64 | if(wasStarted.testAndSetOrdered(0, 1)) { 65 | QAndroidJniObject::callStaticMethod("de/skycoder42/qtservice/test/TestServiceHelper", 66 | "notifyRunning", "(Landroid/app/Service;Ljava/lang/String;)V", 67 | QtAndroid::androidService().object(), 68 | QAndroidJniObject::fromString(QStringLiteral("Service started…")).object()); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /examples/service/AndroidServiceTest/testservice.h: -------------------------------------------------------------------------------- 1 | #ifndef TESTSERVICE_H 2 | #define TESTSERVICE_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | class TestService : public QtService::Service 9 | { 10 | Q_OBJECT 11 | 12 | public: 13 | explicit TestService(int &argc, char **argv); 14 | 15 | protected: 16 | CommandResult onStart() override; 17 | CommandResult onStop(int &exitCode) override; 18 | 19 | int onStartCommand(const QAndroidIntent &intent, int flags, int startId); 20 | QAndroidBinder *onBind(const QAndroidIntent &intent); 21 | 22 | private: 23 | void doStartNotify(); 24 | }; 25 | 26 | Q_DECLARE_METATYPE(QAndroidIntent) 27 | Q_DECLARE_METATYPE(QAndroidBinder*) 28 | 29 | #endif // TESTSERVICE_H 30 | -------------------------------------------------------------------------------- /examples/service/EchoControl/EchoControl.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = app 2 | 3 | TARGET = EchoControl 4 | QT += core gui widgets service 5 | 6 | SOURCES += \ 7 | main.cpp \ 8 | controlwidget.cpp 9 | 10 | HEADERS += \ 11 | controlwidget.h 12 | 13 | FORMS += \ 14 | controlwidget.ui 15 | 16 | target.path = $$[QT_INSTALL_EXAMPLES]/service/$$TARGET 17 | !install_ok: INSTALLS += target 18 | -------------------------------------------------------------------------------- /examples/service/EchoControl/controlwidget.cpp: -------------------------------------------------------------------------------- 1 | #include "controlwidget.h" 2 | #include "ui_controlwidget.h" 3 | 4 | #include 5 | #include 6 | using namespace QtService; 7 | 8 | ControlWidget::ControlWidget(QWidget *parent) : 9 | QWidget(parent), 10 | ui(new Ui::ControlWidget) 11 | { 12 | ui->setupUi(this); 13 | ui->backendComboBox->addItems(ServiceControl::listBackends()); 14 | ui->unloadButton->hide(); 15 | ui->statusButton->setDefaultAction(ui->actionReload); 16 | 17 | connect(ui->actionReload, &QAction::triggered, 18 | this, &ControlWidget::setStatus); 19 | } 20 | 21 | ControlWidget::~ControlWidget() 22 | { 23 | delete ui; 24 | } 25 | 26 | void ControlWidget::on_loadButton_clicked() 27 | { 28 | if(_control) 29 | _control->deleteLater(); 30 | _control = ServiceControl::create(ui->backendComboBox->currentText(), 31 | ui->nameLineEdit->text(), 32 | this); 33 | connect(_control, &ServiceControl::errorChanged, 34 | this, [this](const QString &error){ 35 | QMessageBox::critical(this, tr("Error"), error); 36 | }); 37 | if(!_control->serviceExists()) { 38 | QMessageBox::critical(this, 39 | tr("Error"), 40 | tr("Unable to find a service of name \"%1\" with backend \"%2\"") 41 | .arg(_control->serviceId(), _control->backend())); 42 | _control->deleteLater(); 43 | _control = nullptr; 44 | return; 45 | } 46 | 47 | auto metaEnum = QMetaEnum::fromType(); 48 | ui->supportsLineEdit->setText(QString::fromUtf8(metaEnum.valueToKeys(static_cast(_control->supportFlags())))); 49 | 50 | ui->loadButton->setVisible(false); 51 | ui->unloadButton->setVisible(true); 52 | 53 | ui->backendComboBox->setEnabled(false); 54 | ui->nameLineEdit->setEnabled(false); 55 | 56 | if(_control->supportFlags().testFlag(ServiceControl::SupportFlag::SetBlocking)) { 57 | ui->bLockingCheckBox->setEnabled(true); 58 | ui->bLockingCheckBox->setChecked(_control->blocking() == ServiceControl::BlockMode::Blocking); 59 | } else if(_control->blocking() == ServiceControl::BlockMode::Blocking) 60 | ui->bLockingCheckBox->setChecked(true); 61 | else if(_control->blocking() == ServiceControl::BlockMode::NonBlocking) 62 | ui->bLockingCheckBox->setChecked(false); 63 | else 64 | ui->bLockingCheckBox->setCheckState(Qt::PartiallyChecked); 65 | 66 | if(_control->supportFlags().testFlag(ServiceControl::SupportFlag::GetAutostart)) 67 | ui->enabledCheckBox->setChecked(_control->isAutostartEnabled()); 68 | if(_control->supportFlags().testFlag(ServiceControl::SupportFlag::SetAutostart)) 69 | ui->enabledCheckBox->setEnabled(true); 70 | 71 | if(_control->supportFlags().testFlag(ServiceControl::SupportFlag::Status)) { 72 | ui->actionReload->setEnabled(true); 73 | setStatus(); 74 | } 75 | 76 | if(_control->supportFlags().testFlag(ServiceControl::SupportFlag::Start)) 77 | ui->startButton->setEnabled(true); 78 | if(_control->supportFlags().testFlag(ServiceControl::SupportFlag::Stop)) 79 | ui->stopButton->setEnabled(true); 80 | if(_control->supportFlags().testFlag(ServiceControl::SupportFlag::Pause)) 81 | ui->pauseButton->setEnabled(true); 82 | if(_control->supportFlags().testFlag(ServiceControl::SupportFlag::Resume)) 83 | ui->resumeButton->setEnabled(true); 84 | if(_control->supportFlags().testFlag(ServiceControl::SupportFlag::Reload)) 85 | ui->reloadButton->setEnabled(true); 86 | } 87 | 88 | void ControlWidget::on_unloadButton_clicked() 89 | { 90 | if(_control) 91 | _control->deleteLater(); 92 | _control = nullptr; 93 | 94 | ui->loadButton->setVisible(true); 95 | ui->unloadButton->setVisible(false); 96 | 97 | ui->backendComboBox->setEnabled(true); 98 | ui->nameLineEdit->setEnabled(true); 99 | 100 | ui->bLockingCheckBox->setEnabled(false); 101 | ui->bLockingCheckBox->setChecked(false); 102 | ui->enabledCheckBox->setEnabled(false); 103 | ui->enabledCheckBox->setChecked(false); 104 | 105 | ui->supportsLineEdit->clear(); 106 | ui->actionReload->setEnabled(false); 107 | ui->statusLineEdit->clear(); 108 | 109 | ui->startButton->setEnabled(false); 110 | ui->stopButton->setEnabled(false); 111 | ui->pauseButton->setEnabled(false); 112 | ui->resumeButton->setEnabled(false); 113 | ui->reloadButton->setEnabled(false); 114 | } 115 | 116 | void ControlWidget::setStatus() 117 | { 118 | if(!_control) 119 | return; 120 | auto metaEnum = QMetaEnum::fromType(); 121 | ui->statusLineEdit->setText(QString::fromUtf8(metaEnum.valueToKey(static_cast(_control->status())))); 122 | if(_control->supportFlags().testFlag(ServiceControl::SupportFlag::GetAutostart)) 123 | ui->enabledCheckBox->setChecked(_control->isAutostartEnabled()); 124 | } 125 | 126 | void ControlWidget::on_bLockingCheckBox_clicked(bool checked) 127 | { 128 | if(!_control) 129 | return; 130 | _control->setBlocking(checked); 131 | } 132 | 133 | void ControlWidget::on_enabledCheckBox_clicked(bool checked) 134 | { 135 | if(!_control) 136 | return; 137 | if(checked) 138 | _control->enableAutostart(); 139 | else 140 | _control->disableAutostart(); 141 | } 142 | 143 | void ControlWidget::on_startButton_clicked() 144 | { 145 | if(!_control) 146 | return; 147 | _control->start(); 148 | } 149 | 150 | void ControlWidget::on_stopButton_clicked() 151 | { 152 | if(!_control) 153 | return; 154 | _control->stop(); 155 | } 156 | 157 | void ControlWidget::on_pauseButton_clicked() 158 | { 159 | if(!_control) 160 | return; 161 | _control->pause(); 162 | } 163 | 164 | void ControlWidget::on_resumeButton_clicked() 165 | { 166 | if(!_control) 167 | return; 168 | _control->resume(); 169 | } 170 | 171 | void ControlWidget::on_reloadButton_clicked() 172 | { 173 | if(!_control) 174 | return; 175 | _control->reload(); 176 | } 177 | -------------------------------------------------------------------------------- /examples/service/EchoControl/controlwidget.h: -------------------------------------------------------------------------------- 1 | #ifndef CONTROLWIDGET_H 2 | #define CONTROLWIDGET_H 3 | 4 | #include 5 | #include 6 | 7 | namespace Ui { 8 | class ControlWidget; 9 | } 10 | 11 | class ControlWidget : public QWidget 12 | { 13 | Q_OBJECT 14 | 15 | public: 16 | explicit ControlWidget(QWidget *parent = nullptr); 17 | ~ControlWidget() override; 18 | 19 | private Q_SLOTS: 20 | void on_loadButton_clicked(); 21 | void on_unloadButton_clicked(); 22 | 23 | void on_bLockingCheckBox_clicked(bool checked); 24 | void on_enabledCheckBox_clicked(bool checked); 25 | 26 | void on_startButton_clicked(); 27 | void on_stopButton_clicked(); 28 | void on_pauseButton_clicked(); 29 | void on_resumeButton_clicked(); 30 | void on_reloadButton_clicked(); 31 | 32 | private: 33 | Ui::ControlWidget *ui; 34 | QtService::ServiceControl *_control = nullptr; 35 | 36 | void setStatus(); 37 | }; 38 | 39 | #endif // CONTROLWIDGET_H 40 | -------------------------------------------------------------------------------- /examples/service/EchoControl/main.cpp: -------------------------------------------------------------------------------- 1 | #include "controlwidget.h" 2 | #include 3 | 4 | int main(int argc, char *argv[]) 5 | { 6 | QApplication a(argc, argv); 7 | ControlWidget w; 8 | w.show(); 9 | 10 | return a.exec(); 11 | } 12 | -------------------------------------------------------------------------------- /examples/service/EchoService/EchoService.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = app 2 | 3 | QT += service 4 | QT -= gui 5 | 6 | CONFIG += console 7 | CONFIG -= app_bundle 8 | 9 | TARGET = echoservice 10 | 11 | HEADERS += \ 12 | echoservice.h 13 | 14 | SOURCES += \ 15 | main.cpp \ 16 | echoservice.cpp 17 | 18 | QMAKE_SUBSTITUTES += \ 19 | echoservice.service.in \ 20 | scinstall.bat.in \ 21 | echoservice.plist.in 22 | 23 | DISTFILES += $$QMAKE_SUBSTITUTES \ 24 | echoservice.socket 25 | 26 | target.path = $$[QT_INSTALL_EXAMPLES]/service/EchoService 27 | !install_ok: INSTALLS += target 28 | 29 | win32: install_svcconf.files += $$shadowed(scinstall.bat) 30 | else:macos: install_svcconf.files += $$shadowed(echoservice.plist) 31 | else:linux:!android: install_svcconf.files += $$shadowed(echoservice.service) echoservice.socket 32 | install_svcconf.CONFIG += no_check_exist 33 | install_svcconf.path = $$[QT_INSTALL_EXAMPLES]/service/EchoService 34 | !install_ok: INSTALLS += install_svcconf 35 | -------------------------------------------------------------------------------- /examples/service/EchoService/echoservice.cpp: -------------------------------------------------------------------------------- 1 | #include "echoservice.h" 2 | #include 3 | #include 4 | #include 5 | 6 | EchoService::EchoService(int &argc, char **argv) : 7 | Service(argc, argv) 8 | {} 9 | 10 | bool EchoService::preStart() 11 | { 12 | qDebug() << Q_FUNC_INFO; 13 | qInfo() << "Service running with backend:" << backend(); 14 | 15 | addCallback("SIGUSR1", [](){ 16 | qDebug() << "SIGUSR1"; 17 | }); 18 | addCallback("SIGUSR2", [](){ 19 | qDebug() << "SIGUSR2"; 20 | return 42; 21 | }); 22 | 23 | return true; 24 | } 25 | 26 | QtService::Service::CommandResult EchoService::onStart() 27 | { 28 | qDebug() << Q_FUNC_INFO; 29 | _server = new QTcpServer(this); 30 | connect(_server, &QTcpServer::newConnection, 31 | this, &EchoService::newConnection); 32 | 33 | auto socket = getSocket(); 34 | auto ok = false; 35 | if(socket >= 0) { 36 | qDebug() << "Using activated socket descriptor:" << socket; 37 | ok = _server->setSocketDescriptor(socket); 38 | } else { 39 | qDebug() << "No sockets activated - creating normal socket"; 40 | ok = _server->listen(); 41 | } 42 | if(ok) 43 | qInfo() << "Started echo server on port" << _server->serverPort(); 44 | else { 45 | qCritical().noquote() << "Failed to start server with error" << _server->errorString(); 46 | qApp->exit(EXIT_FAILURE); 47 | } 48 | 49 | return CommandResult::Completed; 50 | } 51 | 52 | QtService::Service::CommandResult EchoService::onStop(int &exitCode) 53 | { 54 | Q_UNUSED(exitCode) 55 | qDebug() << Q_FUNC_INFO; 56 | _server->close(); 57 | return CommandResult::Completed; 58 | } 59 | 60 | QtService::Service::CommandResult EchoService::onReload() 61 | { 62 | qDebug() << Q_FUNC_INFO; 63 | _server->close(); 64 | if(_server->listen()) 65 | qInfo() << "Restarted echo server on port" << _server->serverPort(); 66 | else { 67 | qCritical().noquote() << "Failed to restart server with error" << _server->errorString(); 68 | qApp->exit(EXIT_FAILURE); 69 | } 70 | 71 | return CommandResult::Completed; 72 | } 73 | 74 | QtService::Service::CommandResult EchoService::onPause() 75 | { 76 | qDebug() << Q_FUNC_INFO; 77 | _server->pauseAccepting(); 78 | return CommandResult::Completed; 79 | } 80 | 81 | QtService::Service::CommandResult EchoService::onResume() 82 | { 83 | qDebug() << Q_FUNC_INFO; 84 | _server->resumeAccepting(); 85 | return CommandResult::Completed; 86 | } 87 | 88 | void EchoService::newConnection() 89 | { 90 | while(_server->hasPendingConnections()) { 91 | auto socket = _server->nextPendingConnection(); 92 | socket->setParent(this); 93 | connect(socket, &QTcpSocket::readyRead, 94 | socket, [socket]() { 95 | auto msg = socket->readAll(); 96 | qDebug() << host(socket) << "Echoing:" << msg; 97 | socket->write(msg); 98 | }); 99 | connect(socket, &QTcpSocket::disconnected, 100 | socket, [socket]() { 101 | qInfo() << host(socket) << "disconnected"; 102 | socket->close(); 103 | socket->deleteLater(); 104 | }); 105 | connect(socket, QOverload::of(&QTcpSocket::error), 106 | socket, [socket](QAbstractSocket::SocketError error) { 107 | qWarning() << host(socket) << "Socket-Error[" << error << "]:" << qUtf8Printable(socket->errorString()); 108 | }); 109 | qInfo() << host(socket) << "connected"; 110 | } 111 | } 112 | 113 | QByteArray EchoService::host(QTcpSocket *socket) 114 | { 115 | return (QLatin1Char('<') + socket->peerAddress().toString() + QLatin1Char(':') + QString::number(socket->peerPort()) + QLatin1Char('>')).toUtf8(); 116 | } 117 | -------------------------------------------------------------------------------- /examples/service/EchoService/echoservice.h: -------------------------------------------------------------------------------- 1 | #ifndef ECHOSERVICE_H 2 | #define ECHOSERVICE_H 3 | 4 | #include 5 | #include 6 | 7 | class EchoService : public QtService::Service 8 | { 9 | Q_OBJECT 10 | 11 | public: 12 | explicit EchoService(int &argc, char **argv); 13 | 14 | protected: 15 | bool preStart() override; 16 | CommandResult onStart() override; 17 | CommandResult onStop(int &exitCode) override; 18 | CommandResult onReload() override; 19 | CommandResult onPause() override; 20 | CommandResult onResume() override; 21 | 22 | private Q_SLOTS: 23 | void newConnection(); 24 | 25 | private: 26 | QTcpServer *_server = nullptr; 27 | 28 | static QByteArray host(QTcpSocket *socket); 29 | }; 30 | 31 | #endif // ECHOSERVICE_H 32 | -------------------------------------------------------------------------------- /examples/service/EchoService/echoservice.plist.in: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Label 6 | de.skycoder42.QtService.echoservice 7 | ProgramArguments 8 | 9 | $${target.path}/$$TARGET 10 | --backend 11 | launchd 12 | 13 | Sockets 14 | 15 | Listeners 16 | 17 | SockServiceName 18 | 6627 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /examples/service/EchoService/echoservice.service.in: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=QtService Example Echo Service 3 | Documentation=https://github.com/Skycoder42/QtService 4 | After=network-online.target echoservice.socket 5 | 6 | [Service] 7 | Type=notify 8 | NotifyAccess=exec 9 | ExecStart=$${target.path}/$$TARGET --backend systemd 10 | ExecReload=$${target.path}/$$TARGET --backend systemd reload 11 | ExecStop=$${target.path}/$$TARGET --backend systemd stop 12 | WatchdogSec=10 13 | Restart=on-abnormal 14 | RuntimeDirectory=$$TARGET 15 | 16 | [Install] 17 | #WantedBy=multi-user.target 18 | WantedBy=default.target 19 | -------------------------------------------------------------------------------- /examples/service/EchoService/echoservice.socket: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=QtService Example Echo Service Socket 3 | Documentation=https://github.com/Skycoder42/QtService 4 | After=network.target 5 | PartOf=echoservice.service 6 | 7 | [Socket] 8 | ListenStream=6627 9 | 10 | [Install] 11 | WantedBy=sockets.target 12 | -------------------------------------------------------------------------------- /examples/service/EchoService/main.cpp: -------------------------------------------------------------------------------- 1 | #include "echoservice.h" 2 | 3 | int main(int argc, char *argv[]) 4 | { 5 | EchoService svc(argc, argv); 6 | QCoreApplication::setApplicationName(QStringLiteral("echoservice")); 7 | QCoreApplication::setApplicationVersion(QStringLiteral("1.0.0")); 8 | //... 9 | return svc.exec(); 10 | } 11 | -------------------------------------------------------------------------------- /examples/service/EchoService/scinstall.bat.in: -------------------------------------------------------------------------------- 1 | @echo off 2 | sc create echoservice binPath= \\"$$system_path($${target.path}\\\\$${TARGET}.exe) --backend windows\\" start= demand displayname= \\"%{ProjectName} Service\\" || exit /B 1 3 | sc description echoservice \\"The Echo Service\\" || exit /B 1 4 | -------------------------------------------------------------------------------- /examples/service/TerminalService/TerminalService.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = app 2 | 3 | QT += service 4 | QT -= gui 5 | 6 | CONFIG += console 7 | CONFIG -= app_bundle 8 | 9 | TARGET = terminalservice 10 | 11 | HEADERS += \ 12 | terminalservice.h 13 | 14 | SOURCES += \ 15 | main.cpp \ 16 | terminalservice.cpp 17 | 18 | target.path = $$[QT_INSTALL_EXAMPLES]/service/TerminalService 19 | !install_ok: INSTALLS += target 20 | -------------------------------------------------------------------------------- /examples/service/TerminalService/main.cpp: -------------------------------------------------------------------------------- 1 | #include "terminalservice.h" 2 | 3 | int main(int argc, char *argv[]) 4 | { 5 | TerminalService service(argc, argv); 6 | QCoreApplication::setApplicationName(QStringLiteral("terminalservice")); 7 | QCoreApplication::setApplicationVersion(QStringLiteral("1.0.0")); 8 | //... 9 | return service.exec(); 10 | } 11 | -------------------------------------------------------------------------------- /examples/service/TerminalService/terminalservice.cpp: -------------------------------------------------------------------------------- 1 | #include "terminalservice.h" 2 | #include 3 | #include 4 | using namespace QtService; 5 | 6 | TerminalService::TerminalService(int &argc, char **argv) : 7 | Service(argc, argv) 8 | { 9 | setTerminalActive(true); 10 | setStartWithTerminal(true); 11 | //setTerminalMode(Service::ReadWritePassive); 12 | } 13 | 14 | Service::CommandResult TerminalService::onStart() 15 | { 16 | qDebug() << "Service started with terminal mode:" << terminalMode(); 17 | return CommandResult::Completed; 18 | } 19 | 20 | Service::CommandResult TerminalService::onStop(int &exitCode) 21 | { 22 | qDebug() << "Closing down service..."; 23 | Q_UNUSED(exitCode) 24 | return CommandResult::Completed; 25 | } 26 | 27 | bool TerminalService::verifyCommand(const QStringList &arguments) 28 | { 29 | QCommandLineParser parser; 30 | if(parseArguments(parser, arguments)) { 31 | // print help/version if requested. Quits the terminal before even trying to connect 32 | if(parser.isSet(QStringLiteral("help"))) 33 | parser.showHelp(); 34 | if(parser.isSet(QStringLiteral("version"))) 35 | parser.showVersion(); 36 | 37 | if(parser.isSet(QStringLiteral("passive"))) 38 | setTerminalMode(Service::TerminalMode::ReadWritePassive); 39 | return true; 40 | } else 41 | return false; 42 | } 43 | 44 | void TerminalService::terminalConnected(Terminal *terminal) 45 | { 46 | qDebug() << "new terminal connected with args:" << terminal->command(); 47 | connect(terminal, &Terminal::terminalDisconnected, 48 | this, [](){ 49 | qDebug() << "A terminal just disconnected"; 50 | }); 51 | 52 | QCommandLineParser parser; 53 | if(!parseArguments(parser, terminal->command())) { 54 | terminal->disconnectTerminal(); 55 | return; 56 | } 57 | 58 | if(parser.positionalArguments().startsWith(QStringLiteral("stop"))) 59 | quit(); 60 | else if(terminal->terminalMode() == Service::TerminalMode::ReadWriteActive) { 61 | connect(terminal, &Terminal::readyRead, 62 | terminal, [terminal](){ 63 | qDebug() << "terminals name is:" << terminal->readAll(); 64 | terminal->disconnectTerminal(); 65 | }); 66 | terminal->write("Please enter your name: "); 67 | terminal->requestLine(); 68 | } else { 69 | connect(terminal, &Terminal::readyRead, 70 | terminal, [terminal](){ 71 | auto data = terminal->readAll(); 72 | qDebug() << "teminal said:" << data; 73 | terminal->write(data); 74 | }); 75 | } 76 | } 77 | 78 | bool TerminalService::parseArguments(QCommandLineParser &parser, const QStringList &arguments) 79 | { 80 | parser.addHelpOption(); 81 | parser.addVersionOption(); 82 | parser.addOption({ 83 | {QStringLiteral("p"), QStringLiteral("passive")}, 84 | QStringLiteral("Run terminal service in passive (Non-Interactive) mode") 85 | }); 86 | parser.addPositionalArgument(QStringLiteral("stop"), 87 | QStringLiteral("Stop the the service"), 88 | QStringLiteral("[stop]")); 89 | 90 | return parser.parse(arguments); 91 | } 92 | -------------------------------------------------------------------------------- /examples/service/TerminalService/terminalservice.h: -------------------------------------------------------------------------------- 1 | #ifndef TERMINALSERVICE_H 2 | #define TERMINALSERVICE_H 3 | 4 | #include 5 | #include 6 | 7 | class TerminalService : public QtService::Service 8 | { 9 | Q_OBJECT 10 | 11 | public: 12 | explicit TerminalService(int &argc, char **argv); 13 | 14 | // Service interface 15 | protected: 16 | CommandResult onStart() override; 17 | CommandResult onStop(int &exitCode) override; 18 | bool verifyCommand(const QStringList &arguments) override; 19 | 20 | // Service interface 21 | protected Q_SLOTS: 22 | void terminalConnected(QtService::Terminal *terminal) override; 23 | 24 | private: 25 | bool parseArguments(QCommandLineParser &parser, const QStringList &arguments); 26 | }; 27 | 28 | #endif // TERMINALSERVICE_H 29 | -------------------------------------------------------------------------------- /examples/service/service.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = subdirs 2 | QT_FOR_CONFIG += core 3 | 4 | SUBDIRS += \ 5 | EchoService \ 6 | EchoControl \ 7 | TerminalService 8 | 9 | android: SUBDIRS += AndroidServiceTest 10 | -------------------------------------------------------------------------------- /qtservice.pro: -------------------------------------------------------------------------------- 1 | load(qt_parts) 2 | 3 | SUBDIRS += doc 4 | 5 | doxygen.target = doxygen 6 | doxygen.CONFIG = recursive 7 | doxygen.recurse_target = doxygen 8 | doxygen.recurse += doc 9 | QMAKE_EXTRA_TARGETS += doxygen 10 | 11 | runtests.target = run-tests 12 | runtests.CONFIG = recursive 13 | runtests.recurse_target = run-tests 14 | runtests.recurse += sub_tests sub_src 15 | QMAKE_EXTRA_TARGETS += runtests 16 | 17 | lupdate.target = lupdate 18 | lupdate.CONFIG = recursive 19 | lupdate.recurse_target = lupdate 20 | lupdate.recurse += sub_src 21 | QMAKE_EXTRA_TARGETS += lupdate 22 | 23 | DISTFILES += .qmake.conf \ 24 | sync.profile \ 25 | .github/workflows/build.yml \ 26 | ProjectTemplate/* 27 | -------------------------------------------------------------------------------- /src/imports/imports.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = subdirs 2 | 3 | SUBDIRS += \ 4 | service 5 | -------------------------------------------------------------------------------- /src/imports/service/qmldir: -------------------------------------------------------------------------------- 1 | module de.skycoder42.QtService 2 | plugin declarative_service 3 | classname QtServiceDeclarativeModule 4 | typeinfo plugins.qmltypes 5 | depends QtQml 2.2 6 | -------------------------------------------------------------------------------- /src/imports/service/qmlservicesingleton.cpp: -------------------------------------------------------------------------------- 1 | #include "qmlservicesingleton.h" 2 | using namespace QtService; 3 | 4 | QmlServiceSingleton::QmlServiceSingleton(QObject *parent) : 5 | QObject(parent) 6 | {} 7 | 8 | ServiceControl *QmlServiceSingleton::createControl(const QString &backend, QString serviceId, QObject *parent) const 9 | { 10 | return ServiceControl::create(backend, std::move(serviceId), parent); 11 | } 12 | 13 | ServiceControl *QmlServiceSingleton::createControlFromName(const QString &backend, const QString &serviceName, QObject *parent) const 14 | { 15 | return ServiceControl::createFromName(backend, serviceName, parent); 16 | } 17 | 18 | ServiceControl *QmlServiceSingleton::createControlFromName(const QString &backend, const QString &serviceName, const QString &domain, QObject *parent) const 19 | { 20 | return ServiceControl::createFromName(backend, serviceName, domain, parent); 21 | } 22 | 23 | Service *QmlServiceSingleton::service() const 24 | { 25 | return Service::instance(); 26 | } 27 | -------------------------------------------------------------------------------- /src/imports/service/qmlservicesingleton.h: -------------------------------------------------------------------------------- 1 | #ifndef QMLSERVICESINGLETON_H 2 | #define QMLSERVICESINGLETON_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #ifdef DOXYGEN_RUN 9 | namespace de::skycoder42::QtService { 10 | 11 | /*! @brief A QML singleton to create service controls and access the service instance 12 | * 13 | * @extends QtQml.QtObject 14 | * @since 1.0 15 | * 16 | * @sa QtService::Service, QtService::ServiceControl 17 | */ 18 | class QtService 19 | #else 20 | namespace QtService { 21 | 22 | class QmlServiceSingleton : public QObject 23 | #endif 24 | { 25 | Q_OBJECT 26 | 27 | /*! @brief A reference to the current service instance, if accessed from within a service 28 | * 29 | * @default{`nullptr`} 30 | * 31 | * If you are using qml in a service, you can use this property to get a reference to the 32 | * current service instance. If not used from within a service, nullptr is returned. 33 | * 34 | * @accessors{ 35 | * @memberAc{service} 36 | * @readonlyAc 37 | * @constantAc 38 | * } 39 | * 40 | * @sa QtService::Service 41 | */ 42 | Q_PROPERTY(QtService::Service* service READ service CONSTANT) 43 | 44 | public: 45 | //! @private 46 | explicit QmlServiceSingleton(QObject *parent = nullptr); 47 | 48 | //! @copydoc QtService::ServiceControl::create 49 | Q_INVOKABLE QtService::ServiceControl *createControl(const QString &backend, QString serviceId, QObject *parent = nullptr) const; 50 | //! @copydoc QtService::ServiceControl::createFromName(const QString &, const QString &, QObject *) 51 | Q_INVOKABLE QtService::ServiceControl *createControlFromName(const QString &backend, const QString &serviceName, QObject *parent = nullptr) const; 52 | //! @copydoc QtService::ServiceControl::createFromName(const QString &, const QString &, const QString &, QObject *) 53 | Q_INVOKABLE QtService::ServiceControl *createControlFromName(const QString &backend, const QString &serviceName, const QString &domain, QObject *parent = nullptr) const; 54 | 55 | //! @private 56 | QtService::Service* service() const; 57 | }; 58 | 59 | } 60 | 61 | #endif // QMLSERVICESINGLETON_H 62 | -------------------------------------------------------------------------------- /src/imports/service/qtservice_plugin.cpp: -------------------------------------------------------------------------------- 1 | #include "qtservice_plugin.h" 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include "qmlservicesingleton.h" 10 | 11 | namespace { 12 | 13 | QObject *createSingleton(QQmlEngine *engine, QJSEngine *) 14 | { 15 | return new QtService::QmlServiceSingleton(engine); 16 | } 17 | 18 | } 19 | 20 | QtServiceDeclarativeModule::QtServiceDeclarativeModule(QObject *parent) : 21 | QQmlExtensionPlugin(parent) 22 | {} 23 | 24 | void QtServiceDeclarativeModule::registerTypes(const char *uri) 25 | { 26 | Q_ASSERT(qstrcmp(uri, "de.skycoder42.QtService") == 0); 27 | 28 | //Version 2.0 29 | qmlRegisterUncreatableType(uri, 2, 0, "ServiceControl", QStringLiteral("A service cannot be created with parameters. Use the QtService.createControl instead.")); 30 | qmlRegisterUncreatableType(uri, 2, 0, "Service", QStringLiteral("A service cannot be created. Use QtService.service instead.")); 31 | qmlRegisterUncreatableType(uri, 2, 0, "Terminal", QStringLiteral("Terminals cannot be created. They can only be produced by the service itself.")); 32 | 33 | qmlRegisterSingletonType(uri, 2, 0, "QtService", createSingleton); 34 | 35 | // Check to make shure no module update is forgotten 36 | static_assert(VERSION_MAJOR == 2 && VERSION_MINOR == 0, "QML module version needs to be updated"); 37 | } 38 | -------------------------------------------------------------------------------- /src/imports/service/qtservice_plugin.h: -------------------------------------------------------------------------------- 1 | #ifndef QTSERVICE_PLUGIN_H 2 | #define QTSERVICE_PLUGIN_H 3 | 4 | #include 5 | 6 | class QtServiceDeclarativeModule : public QQmlExtensionPlugin 7 | { 8 | Q_OBJECT 9 | Q_PLUGIN_METADATA(IID QQmlExtensionInterface_iid) 10 | 11 | public: 12 | QtServiceDeclarativeModule(QObject *parent = nullptr); 13 | void registerTypes(const char *uri) override; 14 | }; 15 | 16 | #endif // QTSERVICE_PLUGIN_H 17 | -------------------------------------------------------------------------------- /src/imports/service/service.pro: -------------------------------------------------------------------------------- 1 | QT = core service qml 2 | 3 | CXX_MODULE = service 4 | TARGETPATH = de/skycoder42/QtService 5 | TARGET = declarative_service 6 | IMPORT_VERSION = $$MODULE_VERSION_IMPORT 7 | DEFINES += "VERSION_MAJOR=$$MODULE_VERSION_MAJOR" 8 | DEFINES += "VERSION_MINOR=$$MODULE_VERSION_MINOR" 9 | 10 | HEADERS += \ 11 | qtservice_plugin.h \ 12 | qmlservicesingleton.h 13 | 14 | SOURCES += \ 15 | qtservice_plugin.cpp \ 16 | qmlservicesingleton.cpp 17 | 18 | OTHER_FILES += qmldir 19 | 20 | CONFIG += qmlcache 21 | load(qml_plugin) 22 | 23 | generate_qmltypes { 24 | # run again to overwrite module env 25 | ldpath.name = LD_LIBRARY_PATH 26 | ldpath.value = "$$shadowed($$dirname(_QMAKE_CONF_))/lib/:$$[QT_INSTALL_LIBS]:$$(LD_LIBRARY_PATH)" 27 | qmlpath.name = QML2_IMPORT_PATH 28 | qmlpath.value = "$$shadowed($$dirname(_QMAKE_CONF_))/qml/:$$[QT_INSTALL_QML]:$$(QML2_IMPORT_PATH)" 29 | PLGDUMP_ENV = ldpath qmlpath 30 | QT_TOOL_ENV = ldpath qmlpath 31 | qtPrepareTool(QMLPLUGINDUMP, qmlplugindump) 32 | QT_TOOL_ENV = 33 | 34 | #overwrite the target deps as make target is otherwise not detected 35 | qmltypes.depends = ../../../qml/$$TARGETPATH/$(TARGET) 36 | OLDDMP = $$take_first(qmltypes.commands) 37 | qmltypes.commands = $$QMLPLUGINDUMP $${qmltypes.commands} 38 | message("replaced $$OLDDMP with $$QMLPLUGINDUMP") 39 | 40 | mfirst.target = all 41 | mfirst.depends += qmltypes 42 | QMAKE_EXTRA_TARGETS += mfirst 43 | } 44 | 45 | -------------------------------------------------------------------------------- /src/java/java.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = aux 2 | 3 | CONFIG -= qt android_install 4 | 5 | javaresources.files = \ 6 | $$PWD/res \ 7 | $$PWD/src 8 | 9 | javaresources.path = $$[QT_INSTALL_PREFIX]/src/android/java 10 | 11 | INSTALLS += javaresources 12 | 13 | !prefix_build:!equals(OUT_PWD, $$PWD) { 14 | RETURN = $$escape_expand(\\n\\t) 15 | equals(QMAKE_HOST.os, Windows) { 16 | RETURN = $$escape_expand(\\r\\n\\t) 17 | } 18 | OUT_PATH = $$shell_path($$OUT_PWD) 19 | 20 | QMAKE_POST_LINK += \ 21 | $${QMAKE_COPY_DIR} $$shell_path($$PWD/res) $$OUT_PATH $$RETURN \ 22 | $${QMAKE_COPY_DIR} $$shell_path($$PWD/src) $$OUT_PATH 23 | } 24 | -------------------------------------------------------------------------------- /src/java/src/de/skycoder42/qtservice/AndroidService.java: -------------------------------------------------------------------------------- 1 | package de.skycoder42.qtservice; 2 | 3 | import java.util.concurrent.Semaphore; 4 | 5 | import android.content.Intent; 6 | 7 | import org.qtproject.qt5.android.bindings.QtService; 8 | 9 | public class AndroidService extends QtService { 10 | private final Semaphore _startSem = new Semaphore(0); 11 | private final Semaphore _exitSem = new Semaphore(0); 12 | 13 | private static native int callStartCommand(Intent intent, int flags, int startId, int oldCode); 14 | private static native boolean exitService(); 15 | 16 | //! Is called by the android service backend plugin to complete the service start 17 | public void nativeReady() { 18 | _startSem.release(); 19 | } 20 | 21 | //! Is called by the android service backend plugin to complete the service stop 22 | public void nativeExited() { 23 | _exitSem.release(); 24 | } 25 | 26 | //! @inherit{android.app.Service.onStartCommand} 27 | @Override 28 | public int onStartCommand(Intent intent, int flags, int startId) { 29 | int res = super.onStartCommand(intent, flags, startId); 30 | try { 31 | _startSem.acquire(); 32 | } catch (InterruptedException e) { 33 | e.printStackTrace(); 34 | return res; 35 | } 36 | res = callStartCommand(intent, flags, startId, res); 37 | _startSem.release(); 38 | return res; 39 | } 40 | 41 | //! @inherit{android.app.Service.onDestroy} 42 | @Override 43 | public void onDestroy() { 44 | try { 45 | if (exitService()) 46 | _exitSem.acquire(); 47 | } catch (InterruptedException e) { 48 | e.printStackTrace(); 49 | } 50 | _exitSem.release(); 51 | super.onDestroy(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/plugins/plugins.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = subdirs 2 | 3 | QT_FOR_CONFIG += core 4 | SUBDIRS += \ 5 | servicebackends 6 | 7 | prepareRecursiveTarget(lrelease) 8 | QMAKE_EXTRA_TARGETS += lrelease 9 | -------------------------------------------------------------------------------- /src/plugins/servicebackends/android/android.json: -------------------------------------------------------------------------------- 1 | { 2 | "Keys" : [ "android" ] 3 | } 4 | -------------------------------------------------------------------------------- /src/plugins/servicebackends/android/android.pro: -------------------------------------------------------------------------------- 1 | TARGET = qandroid 2 | 3 | QT += service androidextras 4 | QT -= gui 5 | 6 | HEADERS += \ 7 | androidserviceplugin.h \ 8 | androidservicebackend.h \ 9 | androidservicecontrol.h 10 | 11 | SOURCES += \ 12 | androidserviceplugin.cpp \ 13 | androidservicebackend.cpp \ 14 | androidservicecontrol.cpp 15 | 16 | DISTFILES += android.json 17 | 18 | PLUGIN_TYPE = servicebackends 19 | PLUGIN_EXTENDS = service 20 | PLUGIN_CLASS_NAME = AndroidServicePlugin 21 | load(qt_plugin) 22 | 23 | DISTFILES += android.json 24 | json_target.target = $$OBJECTS_DIR/moc_androidserviceplugin.o 25 | json_target.depends += $$PWD/android.json 26 | QMAKE_EXTRA_TARGETS += json_target 27 | -------------------------------------------------------------------------------- /src/plugins/servicebackends/android/androidservicebackend.cpp: -------------------------------------------------------------------------------- 1 | #include "androidservicebackend.h" 2 | #include "androidserviceplugin.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | using namespace QtService; 9 | 10 | Q_LOGGING_CATEGORY(logBackend, "qt.service.plugin.android.backend") 11 | 12 | QPointer AndroidServiceBackend::_backendInstance = nullptr; 13 | 14 | AndroidServiceBackend::AndroidServiceBackend(Service *service) : 15 | ServiceBackend{service} 16 | { 17 | _backendInstance = this; 18 | } 19 | 20 | int AndroidServiceBackend::runService(int &argc, char **argv, int flags) 21 | { 22 | QAndroidService app(argc, argv, 23 | std::bind(&AndroidServiceBackend::onBind, this, std::placeholders::_1), 24 | flags); 25 | if (!preStartService()) 26 | return EXIT_FAILURE; 27 | 28 | //NOTE check if onStartCommand is supported with QAndroidService yet 29 | qCDebug(logBackend) << "registering service JNI natives"; 30 | _javaService = QtAndroid::androidService(); 31 | QAndroidJniEnvironment env; 32 | static const JNINativeMethod methods[] = { 33 | {"callStartCommand", "(Landroid/content/Intent;III)I", reinterpret_cast(&AndroidServiceBackend::callStartCommand)}, 34 | {"exitService", "()Z", reinterpret_cast(&AndroidServiceBackend::exitService)} 35 | }; 36 | env->RegisterNatives(env->FindClass("de/skycoder42/qtservice/AndroidService"), 37 | methods, 38 | sizeof(methods)/sizeof(JNINativeMethod)); 39 | qCDebug(logBackend) << "Continue service startup"; 40 | _javaService.callMethod("nativeReady"); 41 | 42 | // handle start result 43 | connect(service(), QOverload::of(&Service::started), 44 | this, &AndroidServiceBackend::onStarted); 45 | 46 | // start the eventloop 47 | QMetaObject::invokeMethod(this, "processServiceCommand", Qt::QueuedConnection, 48 | Q_ARG(QtService::ServiceBackend::ServiceCommand, ServiceCommand::Start)); 49 | return app.exec(); 50 | } 51 | 52 | void AndroidServiceBackend::quitService() 53 | { 54 | QAndroidJniExceptionCleaner cleaner {QAndroidJniExceptionCleaner::OutputMode::Verbose}; 55 | _javaService.callMethod("stopSelf"); 56 | } 57 | 58 | void AndroidServiceBackend::reloadService() 59 | { 60 | processServiceCommand(ServiceCommand::Reload); 61 | } 62 | 63 | jint AndroidServiceBackend::callStartCommand(JNIEnv *, jobject, jobject intent, jint flags, jint startId, jint oldId) 64 | { 65 | qCDebug(logBackend) << "JNI callStartCommand on" << _backendInstance; 66 | if (_backendInstance) { 67 | auto var = _backendInstance->processServiceCallbackImpl("onStartCommand", QVariantList { 68 | QVariant::fromValue(QAndroidIntent{intent}), 69 | static_cast(flags), 70 | static_cast(startId) 71 | }); 72 | auto ok = false; 73 | auto res = var.toInt(&ok); 74 | if (ok) 75 | return res; 76 | } 77 | 78 | return oldId; 79 | } 80 | 81 | jboolean AndroidServiceBackend::exitService(JNIEnv *, jobject) 82 | { 83 | qCDebug(logBackend) << "JNI exitService on" << _backendInstance; 84 | if (_backendInstance) 85 | return QMetaObject::invokeMethod(_backendInstance, "onExit", Qt::QueuedConnection); 86 | else 87 | return false; 88 | } 89 | 90 | void AndroidServiceBackend::onStarted(bool success) 91 | { 92 | if (!success) { 93 | _startupFailed = true; 94 | quitService(); 95 | } 96 | } 97 | 98 | void AndroidServiceBackend::onExit() 99 | { 100 | if (_startupFailed) 101 | onStopped(EXIT_FAILURE); 102 | else { 103 | connect(service(), &Service::stopped, 104 | this, &AndroidServiceBackend::onStopped, 105 | Qt::UniqueConnection); 106 | processServiceCommand(ServiceCommand::Stop); 107 | } 108 | } 109 | 110 | void AndroidServiceBackend::onStopped(int exitCode) 111 | { 112 | qCInfo(logBackend) << "QAndroidService exited with code:" << exitCode; 113 | _javaService.callMethod("nativeExited"); 114 | } 115 | 116 | QAndroidBinder *AndroidServiceBackend::onBind(const QAndroidIntent &intent) 117 | { 118 | return processServiceCallback("onBind", intent); 119 | } 120 | -------------------------------------------------------------------------------- /src/plugins/servicebackends/android/androidservicebackend.h: -------------------------------------------------------------------------------- 1 | #ifndef ANDROIDSERVICEBACKEND_H 2 | #define ANDROIDSERVICEBACKEND_H 3 | 4 | #include 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | class AndroidServiceBackend : public QtService::ServiceBackend 13 | { 14 | Q_OBJECT 15 | 16 | public: 17 | explicit AndroidServiceBackend(QtService::Service *service); 18 | 19 | int runService(int &argc, char **argv, int flags) override; 20 | void quitService() override; 21 | void reloadService() override; 22 | 23 | // helper stuff 24 | static jint JNICALL callStartCommand(JNIEnv *env, jobject object, jobject intent, jint flags, jint startId, jint oldId); 25 | static jboolean JNICALL exitService(JNIEnv *env, jobject object); 26 | 27 | private Q_SLOTS: 28 | void onStarted(bool success); 29 | void onExit(); 30 | void onStopped(int exitCode); 31 | 32 | private: 33 | static QPointer _backendInstance; 34 | QAndroidJniObject _javaService; 35 | bool _startupFailed = false; 36 | 37 | QAndroidBinder* onBind(const QAndroidIntent &intent); 38 | }; 39 | 40 | Q_DECLARE_LOGGING_CATEGORY(logBackend) 41 | 42 | #endif // ANDROIDSERVICEBACKEND_H 43 | -------------------------------------------------------------------------------- /src/plugins/servicebackends/android/androidservicecontrol.h: -------------------------------------------------------------------------------- 1 | #ifndef ANDROIDSERVICECONTROL_H 2 | #define ANDROIDSERVICECONTROL_H 3 | 4 | #include 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | class AndroidServiceControl : public QtService::ServiceControl 14 | { 15 | Q_OBJECT 16 | 17 | public: 18 | explicit AndroidServiceControl(QString &&serviceId, QObject *parent = nullptr); 19 | 20 | QString backend() const override; 21 | SupportFlags supportFlags() const override; 22 | bool serviceExists() const override; 23 | bool isEnabled() const override; 24 | QVariant callGenericCommand(const QByteArray &kind, const QVariantList &args) override; 25 | BlockMode blocking() const override; 26 | 27 | public Q_SLOTS: 28 | bool start() override; 29 | bool stop() override; 30 | bool setEnabled(bool enabled) override; 31 | 32 | protected: 33 | QString serviceName() const override; 34 | 35 | private: 36 | QByteArray jniServiceId() const; 37 | QAndroidJniObject serviceComponent() const; 38 | QAndroidJniObject serviceInfo() const; 39 | 40 | bool bind(QAndroidServiceConnection *serviceConnection, QtAndroid::BindFlags flags); 41 | void unbind(QAndroidServiceConnection *serviceConnection); 42 | void startWithIntent(const QAndroidIntent &intent); 43 | }; 44 | 45 | Q_DECLARE_LOGGING_CATEGORY(logControl) 46 | 47 | #endif // ANDROIDSERVICECONTROL_H 48 | -------------------------------------------------------------------------------- /src/plugins/servicebackends/android/androidserviceplugin.cpp: -------------------------------------------------------------------------------- 1 | #include "androidserviceplugin.h" 2 | #include "androidservicebackend.h" 3 | #include "androidservicecontrol.h" 4 | 5 | AndroidServicePlugin::AndroidServicePlugin(QObject *parent) : 6 | QObject{parent} 7 | {} 8 | 9 | QString AndroidServicePlugin::findServiceId(const QString &backend, const QString &serviceName, const QString &domain) const 10 | { 11 | if (backend == QStringLiteral("android")) 12 | return domain + QLatin1Char('.') + serviceName; 13 | else 14 | return {}; 15 | } 16 | 17 | QtService::ServiceBackend *AndroidServicePlugin::createServiceBackend(const QString &backend, QtService::Service *service) 18 | { 19 | if (backend == QStringLiteral("android")) 20 | return new AndroidServiceBackend{service}; 21 | else 22 | return nullptr; 23 | } 24 | 25 | QtService::ServiceControl *AndroidServicePlugin::createServiceControl(const QString &backend, QString &&serviceId, QObject *parent) 26 | { 27 | if (backend == QStringLiteral("android")) 28 | return new AndroidServiceControl{std::move(serviceId), parent}; 29 | else 30 | return nullptr; 31 | } 32 | -------------------------------------------------------------------------------- /src/plugins/servicebackends/android/androidserviceplugin.h: -------------------------------------------------------------------------------- 1 | #ifndef ANDROIDSERVICEPLUGIN_H 2 | #define ANDROIDSERVICEPLUGIN_H 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | class AndroidServicePlugin : public QObject, public QtService::ServicePlugin 12 | { 13 | Q_OBJECT 14 | Q_PLUGIN_METADATA(IID QtService_ServicePlugin_Iid FILE "android.json") 15 | Q_INTERFACES(QtService::ServicePlugin) 16 | 17 | public: 18 | AndroidServicePlugin(QObject *parent = nullptr); 19 | 20 | QString findServiceId(const QString &backend,const QString &serviceName, const QString &domain) const override; 21 | QtService::ServiceBackend *createServiceBackend(const QString &backend, QtService::Service *service) override; 22 | QtService::ServiceControl *createServiceControl(const QString &backend, QString &&serviceId, QObject *parent) override; 23 | }; 24 | 25 | Q_DECLARE_METATYPE(QAndroidBinder*) 26 | Q_DECLARE_METATYPE(QAndroidIntent) 27 | Q_DECLARE_METATYPE(QAndroidServiceConnection*) 28 | Q_DECLARE_METATYPE(QtAndroid::BindFlags) 29 | 30 | #endif // ANDROIDSERVICEPLUGIN_H 31 | -------------------------------------------------------------------------------- /src/plugins/servicebackends/launchd/launchd.json: -------------------------------------------------------------------------------- 1 | { 2 | "Keys" : [ "launchd" ] 3 | } 4 | -------------------------------------------------------------------------------- /src/plugins/servicebackends/launchd/launchd.pro: -------------------------------------------------------------------------------- 1 | TARGET = qlaunchd 2 | 3 | QT += service 4 | QT -= gui 5 | 6 | HEADERS += \ 7 | launchdserviceplugin.h \ 8 | launchdservicebackend.h \ 9 | launchdservicecontrol.h 10 | 11 | SOURCES += \ 12 | launchdserviceplugin.cpp \ 13 | launchdservicebackend.cpp \ 14 | launchdservicecontrol.cpp 15 | 16 | DISTFILES += launchd.json 17 | 18 | PLUGIN_TYPE = servicebackends 19 | PLUGIN_EXTENDS = service 20 | PLUGIN_CLASS_NAME = LaunchdServicePlugin 21 | load(qt_plugin) 22 | 23 | DISTFILES += launchd.json 24 | json_target.target = $$OBJECTS_DIR/moc_launchdserviceplugin.o 25 | json_target.depends += $$PWD/launchd.json 26 | QMAKE_EXTRA_TARGETS += json_target 27 | -------------------------------------------------------------------------------- /src/plugins/servicebackends/launchd/launchdservicebackend.cpp: -------------------------------------------------------------------------------- 1 | #include "launchdservicebackend.h" 2 | #include "launchdserviceplugin.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | using namespace QtService; 9 | 10 | Q_LOGGING_CATEGORY(logBackend, "qt.service.plugin.launchd.backend") 11 | 12 | LaunchdServiceBackend::LaunchdServiceBackend(Service *service) : 13 | ServiceBackend{service} 14 | {} 15 | 16 | int LaunchdServiceBackend::runService(int &argc, char **argv, int flags) 17 | { 18 | qInstallMessageHandler(LaunchdServiceBackend::syslogMessageHandler); 19 | QCoreApplication app(argc, argv, flags); 20 | if (!preStartService()) 21 | return EXIT_FAILURE; 22 | 23 | connect(service(), QOverload::of(&Service::started), 24 | this, &LaunchdServiceBackend::onStarted); 25 | connect(service(), QOverload::of(&Service::paused), 26 | this, &LaunchdServiceBackend::onPaused); 27 | 28 | for(const auto signal : {SIGINT, SIGTERM, SIGQUIT, SIGHUP, SIGTSTP, SIGCONT, SIGUSR1, SIGUSR2}) 29 | registerForSignal(signal); 30 | 31 | // start the eventloop 32 | QMetaObject::invokeMethod(this, "processServiceCommand", Qt::QueuedConnection, 33 | Q_ARG(QtService::ServiceBackend::ServiceCommand, ServiceCommand::Start)); 34 | return app.exec(); 35 | } 36 | 37 | void LaunchdServiceBackend::quitService() 38 | { 39 | connect(service(), &Service::stopped, 40 | qApp, &QCoreApplication::exit, 41 | Qt::UniqueConnection); 42 | processServiceCommand(ServiceCommand::Stop); 43 | } 44 | 45 | void LaunchdServiceBackend::reloadService() 46 | { 47 | processServiceCommand(ServiceCommand::Reload); 48 | } 49 | 50 | QList LaunchdServiceBackend::getActivatedSockets(const QByteArray &name) 51 | { 52 | auto mName = name.isNull() ? QByteArrayLiteral("Listeners") : name; 53 | if (!_socketCache.contains(mName)) { 54 | int *fdsRaw = nullptr; 55 | size_t cnt = 0; 56 | int err = launch_activate_socket(mName.constData(), &fdsRaw, &cnt); 57 | QScopedPointer fds{fdsRaw}; 58 | if (err != 0) 59 | qCWarning(logBackend) << "Failed to get sockets with error:" << qUtf8Printable(qt_error_string(err)); 60 | else { 61 | _socketCache.reserve(_socketCache.size() + static_cast(cnt)); 62 | for(size_t i = 0; i < cnt; i++) 63 | _socketCache.insert(mName, fds.data()[i]); 64 | } 65 | } 66 | 67 | return _socketCache.values(mName); 68 | } 69 | 70 | void LaunchdServiceBackend::signalTriggered(int signal) 71 | { 72 | qCDebug(logBackend) << "Handling signal" << signal; 73 | switch(signal) { 74 | case SIGINT: 75 | case SIGTERM: 76 | case SIGQUIT: 77 | quitService(); 78 | break; 79 | case SIGHUP: 80 | reloadService(); 81 | break; 82 | case SIGTSTP: 83 | processServiceCommand(ServiceCommand::Pause); 84 | break; 85 | case SIGCONT: 86 | processServiceCommand(ServiceCommand::Resume); 87 | break; 88 | case SIGUSR1: 89 | processServiceCallback("SIGUSR1"); 90 | break; 91 | case SIGUSR2: 92 | processServiceCallback("SIGUSR2"); 93 | break; 94 | default: 95 | ServiceBackend::signalTriggered(signal); 96 | break; 97 | } 98 | } 99 | 100 | void LaunchdServiceBackend::onStarted(bool success) 101 | { 102 | if (!success) 103 | qApp->exit(EXIT_FAILURE); 104 | } 105 | 106 | void LaunchdServiceBackend::onPaused(bool success) 107 | { 108 | if (success) 109 | kill(getpid(), SIGSTOP); // now actually stop 110 | } 111 | 112 | void LaunchdServiceBackend::syslogMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &message) 113 | { 114 | auto formattedMessage = qFormatLogMessage(type, context, message); 115 | 116 | int priority; 117 | switch (type) { 118 | case QtDebugMsg: 119 | priority = LOG_DEBUG; 120 | break; 121 | case QtInfoMsg: 122 | priority = LOG_INFO; 123 | break; 124 | case QtWarningMsg: 125 | priority = LOG_WARNING; 126 | break; 127 | case QtCriticalMsg: 128 | priority = LOG_CRIT; 129 | break; 130 | case QtFatalMsg: 131 | priority = LOG_ALERT; 132 | break; 133 | default: 134 | Q_UNREACHABLE(); 135 | break; 136 | } 137 | 138 | syslog(priority, "%s", qUtf8Printable(formattedMessage)); 139 | } 140 | -------------------------------------------------------------------------------- /src/plugins/servicebackends/launchd/launchdservicebackend.h: -------------------------------------------------------------------------------- 1 | #ifndef LAUNCHDSERVICEBACKEND_H 2 | #define LAUNCHDSERVICEBACKEND_H 3 | 4 | #include 5 | 6 | #include 7 | 8 | class LaunchdServiceBackend : public QtService::ServiceBackend 9 | { 10 | Q_OBJECT 11 | 12 | public: 13 | explicit LaunchdServiceBackend(QtService::Service *service); 14 | 15 | public: 16 | int runService(int &argc, char **argv, int flags) override; 17 | void quitService() override; 18 | void reloadService() override; 19 | QList getActivatedSockets(const QByteArray &name) override; 20 | 21 | protected Q_SLOTS: 22 | void signalTriggered(int signal) override; 23 | 24 | private Q_SLOTS: 25 | void onStarted(bool success); 26 | void onPaused(bool success); 27 | 28 | private: 29 | QMultiHash _socketCache; 30 | 31 | static void syslogMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &message); 32 | }; 33 | 34 | Q_DECLARE_LOGGING_CATEGORY(logBackend) 35 | 36 | #endif // LAUNCHDSERVICEBACKEND_H 37 | -------------------------------------------------------------------------------- /src/plugins/servicebackends/launchd/launchdservicecontrol.cpp: -------------------------------------------------------------------------------- 1 | #include "launchdservicecontrol.h" 2 | #include "launchdserviceplugin.h" 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | using namespace QtService; 10 | 11 | Q_LOGGING_CATEGORY(logControl, "qt.service.plugin.launchd.control") 12 | 13 | LaunchdServiceControl::LaunchdServiceControl(QString &&serviceId, QObject *parent) : 14 | ServiceControl{std::move(serviceId), parent} 15 | {} 16 | 17 | QString LaunchdServiceControl::backend() const 18 | { 19 | return QStringLiteral("launchd"); 20 | } 21 | 22 | ServiceControl::SupportFlags LaunchdServiceControl::supportFlags() const 23 | { 24 | return SupportFlag::StartStop | 25 | SupportFlag::CustomCommands | 26 | SupportFlag::SetEnabled; 27 | } 28 | 29 | bool LaunchdServiceControl::serviceExists() const 30 | { 31 | return runLaunchctl("list") == EXIT_SUCCESS; 32 | } 33 | 34 | bool LaunchdServiceControl::isEnabled() const 35 | { 36 | const auto target = QStringLiteral("user/%1/") 37 | .arg(::geteuid()); 38 | QByteArray outData; 39 | if (runLaunchctl("print-disabled", {target}, false, &outData) == EXIT_SUCCESS) { 40 | const QRegularExpression lineRegex{ 41 | QStringLiteral(R"__(\"%1\"\s*=>\s*true)__").arg(serviceId()), 42 | QRegularExpression::DontCaptureOption 43 | }; 44 | // if not explicitly disabled, assume enabled 45 | return !lineRegex.match(QString::fromUtf8(outData)).hasMatch(); 46 | } else 47 | return true; // assume enabled by default 48 | } 49 | 50 | QVariant LaunchdServiceControl::callGenericCommand(const QByteArray &kind, const QVariantList &args) 51 | { 52 | QStringList sArgs; 53 | sArgs.reserve(args.size()); 54 | for (const auto &arg : args) 55 | sArgs.append(arg.toString()); 56 | return runLaunchctl(kind, sArgs); 57 | } 58 | 59 | bool LaunchdServiceControl::start() 60 | { 61 | return runLaunchctl("start") == EXIT_SUCCESS; 62 | } 63 | 64 | bool LaunchdServiceControl::stop() 65 | { 66 | return runLaunchctl("stop") == EXIT_SUCCESS; 67 | } 68 | 69 | bool LaunchdServiceControl::setEnabled(bool enabled) 70 | { 71 | if(enabled == isEnabled()) 72 | return true; 73 | 74 | const auto target = QStringLiteral("user/%1/%2") 75 | .arg(::geteuid()) 76 | .arg(serviceId()); 77 | return runLaunchctl(enabled ? "enable" : "disable", {target}, false) == EXIT_SUCCESS; 78 | } 79 | 80 | QString LaunchdServiceControl::serviceName() const 81 | { 82 | return serviceId().split(QLatin1Char('.')).last(); 83 | } 84 | 85 | int LaunchdServiceControl::runLaunchctl(const QByteArray &command, const QStringList &extraArgs, bool withServiceId, QByteArray *outData) const 86 | { 87 | const auto launchctl = QStandardPaths::findExecutable(QStringLiteral("launchctl")); 88 | if (launchctl.isEmpty()) { 89 | setError(tr("Failed to find launchctl executable")); 90 | return -1; 91 | } 92 | 93 | QProcess process; 94 | process.setProgram(launchctl); 95 | 96 | QStringList args; 97 | args.reserve(extraArgs.size() + 2); 98 | args.append(QString::fromUtf8(command)); 99 | args.append(extraArgs); 100 | if(withServiceId) 101 | args.append(serviceId()); 102 | process.setArguments(args); 103 | 104 | process.setStandardInputFile(QProcess::nullDevice()); 105 | if(!outData) 106 | process.setStandardOutputFile(QProcess::nullDevice()); 107 | process.setProcessChannelMode(QProcess::ForwardedErrorChannel); 108 | 109 | qCDebug(logControl) << "Executing" << process.program() 110 | << process.arguments(); 111 | process.start(QProcess::ReadOnly); 112 | if (process.waitForFinished(2500)) { // non-blocking calls should finish within two seconds 113 | if (outData) 114 | *outData = process.readAllStandardOutput(); 115 | if (process.exitStatus() == QProcess::NormalExit) 116 | return process.exitCode(); 117 | else { 118 | setError(tr("launchctl crashed with error: %1").arg(process.errorString())); 119 | return 128 + process.error(); 120 | } 121 | } else { 122 | setError(tr("launchctl did not exit in time")); 123 | return -1; 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/plugins/servicebackends/launchd/launchdservicecontrol.h: -------------------------------------------------------------------------------- 1 | #ifndef LAUNCHDSERVICECONTROL_H 2 | #define LAUNCHDSERVICECONTROL_H 3 | 4 | #include 5 | 6 | #include 7 | 8 | class LaunchdServiceControl : public QtService::ServiceControl 9 | { 10 | Q_OBJECT 11 | 12 | public: 13 | explicit LaunchdServiceControl(QString &&serviceId, QObject *parent = nullptr); 14 | 15 | QString backend() const override; 16 | SupportFlags supportFlags() const override; 17 | bool serviceExists() const override; 18 | bool isEnabled() const override; 19 | QVariant callGenericCommand(const QByteArray &kind, const QVariantList &args) override; 20 | 21 | public Q_SLOTS: 22 | bool start() override; 23 | bool stop() override; 24 | bool setEnabled(bool enabled) override; 25 | 26 | protected: 27 | QString serviceName() const override; 28 | 29 | private: 30 | int runLaunchctl(const QByteArray &command, 31 | const QStringList &extraArgs = {}, 32 | bool withServiceId = true, 33 | QByteArray *outData = nullptr) const; 34 | }; 35 | 36 | Q_DECLARE_LOGGING_CATEGORY(logControl) 37 | 38 | #endif // LAUNCHDSERVICECONTROL_H 39 | -------------------------------------------------------------------------------- /src/plugins/servicebackends/launchd/launchdserviceplugin.cpp: -------------------------------------------------------------------------------- 1 | #include "launchdserviceplugin.h" 2 | #include "launchdservicebackend.h" 3 | #include "launchdservicecontrol.h" 4 | 5 | LaunchdServicePlugin::LaunchdServicePlugin(QObject *parent) : 6 | QObject{parent} 7 | {} 8 | 9 | QString LaunchdServicePlugin::findServiceId(const QString &backend, const QString &serviceName, const QString &domain) const 10 | { 11 | if (backend == QStringLiteral("launchd")) 12 | return domain.isEmpty() ? serviceName : domain + QLatin1Char('.') + serviceName; 13 | else 14 | return {}; 15 | } 16 | 17 | QtService::ServiceBackend *LaunchdServicePlugin::createServiceBackend(const QString &backend, QtService::Service *service) 18 | { 19 | if (backend == QStringLiteral("launchd")) 20 | return new LaunchdServiceBackend{service}; 21 | else 22 | return nullptr; 23 | } 24 | 25 | QtService::ServiceControl *LaunchdServicePlugin::createServiceControl(const QString &backend, QString &&serviceId, QObject *parent) 26 | { 27 | if (backend == QStringLiteral("launchd")) 28 | return new LaunchdServiceControl{std::move(serviceId), parent}; 29 | else 30 | return nullptr; 31 | } 32 | -------------------------------------------------------------------------------- /src/plugins/servicebackends/launchd/launchdserviceplugin.h: -------------------------------------------------------------------------------- 1 | #ifndef LAUNCHDSERVICEPLUGIN_H 2 | #define LAUNCHDSERVICEPLUGIN_H 3 | 4 | #include 5 | 6 | class LaunchdServicePlugin : public QObject, public QtService::ServicePlugin 7 | { 8 | Q_OBJECT 9 | Q_PLUGIN_METADATA(IID QtService_ServicePlugin_Iid FILE "launchd.json") 10 | Q_INTERFACES(QtService::ServicePlugin) 11 | 12 | public: 13 | LaunchdServicePlugin(QObject *parent = nullptr); 14 | 15 | QString findServiceId(const QString &backend, const QString &serviceName, const QString &domain) const override; 16 | QtService::ServiceBackend *createServiceBackend(const QString &backend, QtService::Service *service) override; 17 | QtService::ServiceControl *createServiceControl(const QString &backend, QString &&serviceId, QObject *parent) override; 18 | }; 19 | 20 | #endif // LAUNCHDSERVICEPLUGIN_H 21 | -------------------------------------------------------------------------------- /src/plugins/servicebackends/servicebackends.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = subdirs 2 | 3 | SUBDIRS += standard 4 | unix:!android:!ios:packagesExist(libsystemd): SUBDIRS += systemd 5 | android: SUBDIRS += android 6 | win32:!winrt: SUBDIRS += windows 7 | macx: SUBDIRS += launchd 8 | 9 | message("Building plugins: $$SUBDIRS") 10 | 11 | prepareRecursiveTarget(lrelease) 12 | QMAKE_EXTRA_TARGETS += lrelease 13 | -------------------------------------------------------------------------------- /src/plugins/servicebackends/standard/standard.json: -------------------------------------------------------------------------------- 1 | { 2 | "Keys" : [ "standard", "debug" ] 3 | } 4 | -------------------------------------------------------------------------------- /src/plugins/servicebackends/standard/standard.pro: -------------------------------------------------------------------------------- 1 | TARGET = qstandard 2 | 3 | QT += service 4 | QT -= gui 5 | 6 | HEADERS += \ 7 | standardserviceplugin.h \ 8 | standardservicebackend.h \ 9 | standardservicecontrol.h 10 | 11 | SOURCES += \ 12 | standardserviceplugin.cpp \ 13 | standardservicebackend.cpp \ 14 | standardservicecontrol.cpp 15 | 16 | DISTFILES += standard.json 17 | 18 | win32: LIBS += -lkernel32 19 | 20 | PLUGIN_TYPE = servicebackends 21 | PLUGIN_EXTENDS = service 22 | PLUGIN_CLASS_NAME = StandardServicePlugin 23 | load(qt_plugin) 24 | 25 | DISTFILES += standard.json 26 | json_target.target = $$OBJECTS_DIR/moc_standardserviceplugin.o 27 | json_target.depends += $$PWD/standard.json 28 | QMAKE_EXTRA_TARGETS += json_target 29 | -------------------------------------------------------------------------------- /src/plugins/servicebackends/standard/standardservicebackend.cpp: -------------------------------------------------------------------------------- 1 | #include "standardservicebackend.h" 2 | #include "standardserviceplugin.h" 3 | #include 4 | #ifdef Q_OS_WIN 5 | #include 6 | #else 7 | #include 8 | #include 9 | #endif 10 | using namespace QtService; 11 | 12 | Q_LOGGING_CATEGORY(logBackend, "qt.service.plugin.standard.backend") 13 | 14 | StandardServiceBackend::StandardServiceBackend(bool debugMode, Service *service) : 15 | ServiceBackend{service}, 16 | _debugMode{debugMode} 17 | {} 18 | 19 | int StandardServiceBackend::runService(int &argc, char **argv, int flags) 20 | { 21 | //setup logging 22 | QString filePrefix; 23 | if (_debugMode) 24 | filePrefix = QStringLiteral("%{file}:%{line} "); 25 | #ifdef Q_OS_WIN 26 | qSetMessagePattern(QStringLiteral("[%{time} " 27 | "%{if-debug}Debug] %{endif}" 28 | "%{if-info}Info] %{endif}" 29 | "%{if-warning}Warning] %{endif}" 30 | "%{if-critical}Critical] %{endif}" 31 | "%{if-fatal}Fatal] %{endif}" 32 | "%1%{if-category}%{category}: %{endif}" 33 | "%{message}").arg(filePrefix)); 34 | #else 35 | qSetMessagePattern(QStringLiteral("[%{time} " 36 | "%{if-debug}\033[32mDebug\033[0m] %{endif}" 37 | "%{if-info}\033[36mInfo\033[0m] %{endif}" 38 | "%{if-warning}\033[33mWarning\033[0m] %{endif}" 39 | "%{if-critical}\033[31mCritical\033[0m] %{endif}" 40 | "%{if-fatal}\033[35mFatal\033[0m] %{endif}" 41 | "%1%{if-category}%{category}: %{endif}" 42 | "%{message}").arg(filePrefix)); 43 | #endif 44 | 45 | QCoreApplication app(argc, argv, flags); 46 | if (!preStartService()) 47 | return EXIT_FAILURE; 48 | 49 | // create lock 50 | qCDebug(logBackend) << "Creating service lock"; 51 | QLockFile lock{service()->runtimeDir().absoluteFilePath(QStringLiteral("qstandard.lock"))}; 52 | lock.setStaleLockTime(std::numeric_limits::max()); //disable stale locks 53 | if (!lock.tryLock(5000)) { 54 | qCCritical(logBackend) << "Failed to create service lock in" 55 | << service()->runtimeDir().absolutePath() 56 | << "with error code:" << lock.error(); 57 | if (lock.error() == QLockFile::LockFailedError) { 58 | qint64 pid = 0; 59 | QString hostname, appname; 60 | if (lock.getLockInfo(&pid, &hostname, &appname)) { 61 | qCCritical(logBackend).noquote() << "Service already running as:" 62 | << "\n\tPID:" << pid 63 | << "\n\tHostname:" << hostname 64 | << "\n\tAppname:" << appname; 65 | } else 66 | qCCritical(logBackend) << "Unable to determine current lock owner"; 67 | } 68 | return EXIT_FAILURE; 69 | } 70 | 71 | //ensure unlocking always works 72 | connect(qApp, &QCoreApplication::aboutToQuit, 73 | this, [&]() { 74 | lock.unlock(); 75 | }); 76 | connect(service(), QOverload::of(&Service::started), 77 | this, &StandardServiceBackend::onStarted); 78 | connect(service(), QOverload::of(&Service::paused), 79 | this, &StandardServiceBackend::onPaused); 80 | 81 | #ifdef Q_OS_WIN 82 | for (const auto signal : {CTRL_C_EVENT, CTRL_BREAK_EVENT}) { 83 | #else 84 | for (const auto signal : {SIGINT, SIGTERM, SIGQUIT, SIGHUP, SIGTSTP, SIGCONT, SIGUSR1, SIGUSR2}) { 85 | #endif 86 | registerForSignal(signal); 87 | } 88 | 89 | // start the eventloop 90 | qCDebug(logBackend) << "Starting service"; 91 | QMetaObject::invokeMethod(this, "processServiceCommand", Qt::QueuedConnection, 92 | Q_ARG(QtService::ServiceBackend::ServiceCommand, ServiceCommand::Start)); 93 | return app.exec(); 94 | } 95 | 96 | void StandardServiceBackend::quitService() 97 | { 98 | connect(service(), &Service::stopped, 99 | qApp, &QCoreApplication::exit, 100 | Qt::UniqueConnection); 101 | processServiceCommand(ServiceCommand::Stop); 102 | } 103 | 104 | void StandardServiceBackend::reloadService() 105 | { 106 | processServiceCommand(ServiceCommand::Reload); 107 | } 108 | 109 | void StandardServiceBackend::signalTriggered(int signal) 110 | { 111 | qCDebug(logBackend) << "Handeling signal" << signal; 112 | switch(signal) { 113 | #ifdef Q_OS_WIN 114 | case CTRL_C_EVENT: 115 | case CTRL_BREAK_EVENT: 116 | quitService(); 117 | break; 118 | #else 119 | case SIGINT: 120 | case SIGTERM: 121 | case SIGQUIT: 122 | quitService(); 123 | break; 124 | case SIGHUP: 125 | reloadService(); 126 | break; 127 | case SIGTSTP: 128 | processServiceCommand(ServiceCommand::Pause); 129 | break; 130 | case SIGCONT: 131 | processServiceCommand(ServiceCommand::Resume); 132 | break; 133 | case SIGUSR1: 134 | processServiceCallback("SIGUSR1"); 135 | break; 136 | case SIGUSR2: 137 | processServiceCallback("SIGUSR2"); 138 | break; 139 | #endif 140 | default: 141 | ServiceBackend::signalTriggered(signal); 142 | break; 143 | } 144 | } 145 | 146 | void StandardServiceBackend::onStarted(bool success) 147 | { 148 | if (!success) 149 | qApp->exit(EXIT_FAILURE); 150 | } 151 | 152 | void StandardServiceBackend::onPaused(bool success) 153 | { 154 | #ifdef Q_OS_UNIX 155 | if (success) 156 | kill(getpid(), SIGSTOP); // now actually stop 157 | #endif 158 | } 159 | 160 | -------------------------------------------------------------------------------- /src/plugins/servicebackends/standard/standardservicebackend.h: -------------------------------------------------------------------------------- 1 | #ifndef STANDARDSERVICEBACKEND_H 2 | #define STANDARDSERVICEBACKEND_H 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | class StandardServiceBackend : public QtService::ServiceBackend 10 | { 11 | Q_OBJECT 12 | 13 | public: 14 | explicit StandardServiceBackend(bool debugMode, QtService::Service *service); 15 | 16 | int runService(int &argc, char **argv, int flags) override; 17 | void quitService() override; 18 | void reloadService() override; 19 | 20 | protected Q_SLOTS: 21 | void signalTriggered(int signal) override; 22 | 23 | private Q_SLOTS: 24 | void onStarted(bool success); 25 | void onPaused(bool success); 26 | 27 | private: 28 | const bool _debugMode; 29 | }; 30 | 31 | Q_DECLARE_LOGGING_CATEGORY(logBackend) 32 | 33 | #endif // STANDARDSERVICEBACKEND_H 34 | -------------------------------------------------------------------------------- /src/plugins/servicebackends/standard/standardservicecontrol.cpp: -------------------------------------------------------------------------------- 1 | #include "standardservicecontrol.h" 2 | #include "standardserviceplugin.h" 3 | #include 4 | #include 5 | #if QT_CONFIG(process) 6 | #include 7 | #endif 8 | #ifdef Q_OS_WIN 9 | #include 10 | #include 11 | #else 12 | #include 13 | #endif 14 | using namespace QtService; 15 | 16 | Q_LOGGING_CATEGORY(logControl, "qt.service.plugin.standard.control") 17 | 18 | StandardServiceControl::StandardServiceControl(bool debugMode, QString &&serviceId, QObject *parent) : 19 | ServiceControl{std::move(serviceId), parent}, 20 | _debugMode{debugMode} 21 | { 22 | qCDebug(logControl) << "Using lock file path:" << runtimeDir().absoluteFilePath(QStringLiteral("qstandard.lock")); 23 | } 24 | 25 | QString StandardServiceControl::backend() const 26 | { 27 | return _debugMode ? QStringLiteral("debug") : QStringLiteral("standard"); 28 | } 29 | 30 | ServiceControl::SupportFlags StandardServiceControl::supportFlags() const 31 | { 32 | auto flags = SupportFlag::Status | SupportFlag::Stop; 33 | #if QT_CONFIG(process) 34 | flags |= SupportFlag::Start; 35 | #endif 36 | return flags; 37 | } 38 | 39 | bool StandardServiceControl::serviceExists() const 40 | { 41 | return !QStandardPaths::findExecutable(serviceId()).isEmpty(); 42 | } 43 | 44 | ServiceControl::Status StandardServiceControl::status() const 45 | { 46 | const auto lock = statusLock(); 47 | if (lock->tryLock()) { 48 | lock->unlock(); 49 | return Status::Stopped; 50 | } else if(lock->error() == QLockFile::LockFailedError) 51 | return Status::Running; 52 | else { 53 | setError(tr("Failed to access lockfile with error: %1").arg(lock->error())); 54 | return Status::Unknown; 55 | } 56 | } 57 | 58 | ServiceControl::BlockMode StandardServiceControl::blocking() const 59 | { 60 | #ifdef Q_OS_WIN 61 | return BlockMode::Undetermined; 62 | #else 63 | return BlockMode::NonBlocking; 64 | #endif 65 | } 66 | 67 | QVariant StandardServiceControl::callGenericCommand(const QByteArray &kind, const QVariantList &args) 68 | { 69 | Q_UNUSED(args) 70 | if (kind == "getPid") 71 | return getPid(); 72 | else 73 | return {}; 74 | } 75 | 76 | bool StandardServiceControl::start() 77 | { 78 | #if QT_CONFIG(process) 79 | if (status() == Status::Running) { 80 | qCDebug(logControl) << "Service already running with PID" << getPid(); 81 | return true; 82 | } 83 | 84 | auto bin = QStandardPaths::findExecutable(serviceId()); 85 | if (bin.isEmpty()) { 86 | setError(tr("Unabled to find executable for service with id \"%1\"").arg(serviceId())); 87 | return false; 88 | } 89 | 90 | const auto prepareProc = [&](QProcess *svcProc){ 91 | svcProc->setProgram(bin); 92 | svcProc->setArguments({QStringLiteral("--backend"), backend()}); 93 | svcProc->setWorkingDirectory(QDir::rootPath()); 94 | }; 95 | 96 | auto ok = false; 97 | qint64 pid = 0; 98 | QString errorString; 99 | if (_debugMode) { 100 | auto svcProc = new QProcess{nullptr}; // detached instance 101 | connect(svcProc, QOverload::of(&QProcess::finished), 102 | svcProc, &QProcess::deleteLater); 103 | prepareProc(svcProc); 104 | svcProc->setProcessChannelMode(QProcess::ForwardedChannels); 105 | svcProc->setInputChannelMode(QProcess::ForwardedInputChannel); 106 | qCDebug(logControl) << "Launching service subprocess as" 107 | << svcProc->program() 108 | << svcProc->arguments(); 109 | svcProc->start(); 110 | ok = svcProc->waitForStarted(); 111 | if(ok) 112 | pid = svcProc->processId(); 113 | else 114 | errorString = svcProc->errorString(); 115 | } else { 116 | QProcess svcProc; 117 | prepareProc(&svcProc); 118 | svcProc.setStandardInputFile(QProcess::nullDevice()); 119 | svcProc.setStandardOutputFile(QProcess::nullDevice()); 120 | svcProc.setStandardErrorFile(QProcess::nullDevice()); 121 | qCDebug(logControl) << "Launching service detached as" 122 | << svcProc.program() 123 | << svcProc.arguments(); 124 | ok = svcProc.startDetached(&pid); 125 | if(!ok) 126 | errorString = svcProc.errorString(); 127 | } 128 | 129 | if(ok) { 130 | qCDebug(logControl) << "Started service process with PID" << pid 131 | << (_debugMode ? "in debug mode" : ""); 132 | } else 133 | setError(tr("Failed to start service process with error: %1").arg(errorString)); 134 | return ok; 135 | #else 136 | return ServiceControl::start(); 137 | #endif 138 | } 139 | 140 | bool StandardServiceControl::stop() 141 | { 142 | if(status() == Status::Stopped) { 143 | qCDebug(logControl) << "Service already stopped "; 144 | return true; 145 | } 146 | 147 | auto pid = getPid(); 148 | if (pid == -1) { 149 | setError(tr("Failed to get pid of running service")); 150 | return false; 151 | } 152 | #ifdef Q_OS_WIN 153 | auto ok = false; 154 | auto hadConsole = FreeConsole(); 155 | const auto _sg0 = qScopeGuard([hadConsole]() { 156 | if (hadConsole) 157 | AllocConsole(); 158 | }); 159 | if (AttachConsole(static_cast(pid))) { 160 | const auto _sg1 = qScopeGuard([]() { 161 | FreeConsole(); 162 | }); 163 | if (SetConsoleCtrlHandler(nullptr, true)) { 164 | const auto _sg2 = qScopeGuard([]() { 165 | SetConsoleCtrlHandler(nullptr, false); 166 | }); 167 | for (auto i = 0; i < 10; i++) { 168 | if (GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0)) { 169 | if (status() == Status::Running) 170 | QThread::msleep(500); 171 | else { 172 | ok = true; 173 | break; 174 | } 175 | } else 176 | setError(tr("Failed to send stop signal with error: %1").arg(qt_error_string(GetLastError()))); 177 | } 178 | if (!ok) 179 | setError(tr("Service did not stop yet")); 180 | } else 181 | setError(tr("Failed to disable local console handler with error: %1").arg(qt_error_string(GetLastError()))); 182 | } else 183 | setError(tr("Failed to attach to service console with error: %1").arg(qt_error_string(GetLastError()))); 184 | return ok; 185 | #else 186 | return kill(static_cast(pid), SIGTERM) == 0; 187 | #endif 188 | } 189 | 190 | QString StandardServiceControl::serviceName() const 191 | { 192 | QFileInfo info{serviceId()}; 193 | if (info.isExecutable()) 194 | return QFileInfo{serviceId()}.completeBaseName(); 195 | else 196 | return serviceId().split(QLatin1Char('/'), Qt::SkipEmptyParts).last(); 197 | } 198 | 199 | QSharedPointer StandardServiceControl::statusLock() const 200 | { 201 | const auto lock = QSharedPointer::create(runtimeDir().absoluteFilePath(QStringLiteral("qstandard.lock"))); 202 | lock->setStaleLockTime(std::numeric_limits::max()); // disable stale locks 203 | return lock; 204 | } 205 | 206 | qint64 StandardServiceControl::getPid() 207 | { 208 | qint64 pid = 0; 209 | QString _h, _a; 210 | if (statusLock()->getLockInfo(&pid, &_h, &_a)) 211 | return pid; 212 | else 213 | return -1; 214 | } 215 | -------------------------------------------------------------------------------- /src/plugins/servicebackends/standard/standardservicecontrol.h: -------------------------------------------------------------------------------- 1 | #ifndef STANDARDSERVICECONTROL_H 2 | #define STANDARDSERVICECONTROL_H 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | class StandardServiceControl : public QtService::ServiceControl 10 | { 11 | Q_OBJECT 12 | 13 | public: 14 | explicit StandardServiceControl(bool debugMode, QString &&serviceId, QObject *parent = nullptr); 15 | 16 | QString backend() const override; 17 | SupportFlags supportFlags() const override; 18 | bool serviceExists() const override; 19 | Status status() const override; 20 | BlockMode blocking() const override; 21 | 22 | QVariant callGenericCommand(const QByteArray &kind, const QVariantList &args) override; 23 | 24 | public Q_SLOTS: 25 | bool start() override; 26 | bool stop() override; 27 | 28 | protected: 29 | QString serviceName() const override; 30 | 31 | private: 32 | const bool _debugMode; 33 | 34 | QSharedPointer statusLock() const; 35 | qint64 getPid(); 36 | }; 37 | 38 | Q_DECLARE_LOGGING_CATEGORY(logControl) 39 | 40 | #endif // STANDARDSERVICECONTROL_H 41 | -------------------------------------------------------------------------------- /src/plugins/servicebackends/standard/standardserviceplugin.cpp: -------------------------------------------------------------------------------- 1 | #include "standardserviceplugin.h" 2 | #include "standardservicebackend.h" 3 | #include "standardservicecontrol.h" 4 | #include 5 | 6 | StandardServicePlugin::StandardServicePlugin(QObject *parent) : 7 | QObject{parent} 8 | {} 9 | 10 | QString StandardServicePlugin::currentServiceId(const QString &backend) const 11 | { 12 | if (backend == QStringLiteral("standard") || 13 | backend == QStringLiteral("debug")) 14 | return QCoreApplication::applicationFilePath(); 15 | else 16 | return {}; 17 | } 18 | 19 | QString StandardServicePlugin::findServiceId(const QString &backend, const QString &serviceName, const QString &domain) const 20 | { 21 | Q_UNUSED(domain) 22 | 23 | if (backend == QStringLiteral("standard") || 24 | backend == QStringLiteral("debug")) { 25 | // first: search wherever this executable is 26 | auto serviceId = QStandardPaths::findExecutable(serviceName, {QCoreApplication::applicationDirPath()}); 27 | // second: search in system paths 28 | if (serviceId.isEmpty()) 29 | serviceId = QStandardPaths::findExecutable(serviceName); 30 | // if found, return the result 31 | if (!serviceId.isEmpty()) 32 | return serviceId; 33 | } 34 | 35 | return {}; 36 | } 37 | 38 | QtService::ServiceBackend *StandardServicePlugin::createServiceBackend(const QString &backend, QtService::Service *service) 39 | { 40 | if (backend == QStringLiteral("standard")) 41 | return new StandardServiceBackend{false, service}; 42 | else if (backend == QStringLiteral("debug")) 43 | return new StandardServiceBackend{true, service}; 44 | else 45 | return nullptr; 46 | } 47 | 48 | QtService::ServiceControl *StandardServicePlugin::createServiceControl(const QString &backend, QString &&serviceId, QObject *parent) 49 | { 50 | if (backend == QStringLiteral("standard")) 51 | return new StandardServiceControl{false, std::move(serviceId), parent}; 52 | else if (backend == QStringLiteral("debug")) 53 | return new StandardServiceControl{true, std::move(serviceId), parent}; 54 | else 55 | return nullptr; 56 | } 57 | -------------------------------------------------------------------------------- /src/plugins/servicebackends/standard/standardserviceplugin.h: -------------------------------------------------------------------------------- 1 | #ifndef STANDARDSERVICEPLUGIN_H 2 | #define STANDARDSERVICEPLUGIN_H 3 | 4 | #include 5 | 6 | class StandardServicePlugin : public QObject, public QtService::ServicePlugin 7 | { 8 | Q_OBJECT 9 | Q_PLUGIN_METADATA(IID QtService_ServicePlugin_Iid FILE "standard.json") 10 | Q_INTERFACES(QtService::ServicePlugin) 11 | 12 | public: 13 | StandardServicePlugin(QObject *parent = nullptr); 14 | 15 | QString currentServiceId(const QString &backend) const override; 16 | QString findServiceId(const QString &backend, const QString &serviceName, const QString &domain) const override; 17 | QtService::ServiceBackend *createServiceBackend(const QString &backend, QtService::Service *service) override; 18 | QtService::ServiceControl *createServiceControl(const QString &backend, QString &&serviceId, QObject *parent) override; 19 | }; 20 | 21 | #endif // STANDARDSERVICEPLUGIN_H 22 | -------------------------------------------------------------------------------- /src/plugins/servicebackends/systemd/de.skycoder42.QtService.ServicePlugin.systemd.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/plugins/servicebackends/systemd/systemd.json: -------------------------------------------------------------------------------- 1 | { 2 | "Keys" : [ "systemd" ] 3 | } 4 | -------------------------------------------------------------------------------- /src/plugins/servicebackends/systemd/systemd.pro: -------------------------------------------------------------------------------- 1 | TARGET = qsystemd 2 | 3 | QT += service dbus 4 | QT -= gui 5 | 6 | CONFIG += link_pkgconfig 7 | PKGCONFIG += libsystemd 8 | 9 | HEADERS += \ 10 | systemdserviceplugin.h \ 11 | systemdservicebackend.h \ 12 | systemdservicecontrol.h 13 | 14 | SOURCES += \ 15 | systemdserviceplugin.cpp \ 16 | systemdservicebackend.cpp \ 17 | systemdservicecontrol.cpp 18 | 19 | DBUS_INTERFACES += de.skycoder42.QtService.ServicePlugin.systemd.xml 20 | DBUS_ADAPTORS += $$DBUS_INTERFACES 21 | 22 | DISTFILES += \ 23 | systemd.json 24 | 25 | PLUGIN_TYPE = servicebackends 26 | PLUGIN_EXTENDS = service 27 | PLUGIN_CLASS_NAME = SystemdServicePlugin 28 | load(qt_plugin) 29 | 30 | DISTFILES += systemd.json 31 | json_target.target = $$OBJECTS_DIR/moc_systemdserviceplugin.o 32 | json_target.depends += $$PWD/systemd.json 33 | QMAKE_EXTRA_TARGETS += json_target 34 | -------------------------------------------------------------------------------- /src/plugins/servicebackends/systemd/systemdservicebackend.h: -------------------------------------------------------------------------------- 1 | #ifndef SYSTEMDSERVICEBACKEND_H 2 | #define SYSTEMDSERVICEBACKEND_H 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include 10 | 11 | #include "systemd_adaptor.h" 12 | 13 | class SystemdServiceBackend : public QtService::ServiceBackend 14 | { 15 | Q_OBJECT 16 | 17 | public: 18 | static const QString DBusObjectPath; 19 | 20 | explicit SystemdServiceBackend(QtService::Service *service); 21 | 22 | int runService(int &argc, char **argv, int flags) override; 23 | Q_INVOKABLE void quitService() override; 24 | Q_INVOKABLE void reloadService() override; 25 | QList getActivatedSockets(const QByteArray &name) override; 26 | 27 | protected Q_SLOTS: 28 | void signalTriggered(int signal) override; 29 | 30 | private Q_SLOTS: 31 | void sendWatchdog(); 32 | 33 | void onStarted(bool success); 34 | void onReloaded(bool success); 35 | void onStopped(int exitCode); 36 | void onPaused(bool success); 37 | 38 | private: 39 | bool _userService = true; 40 | QTimer *_watchdogTimer = nullptr; 41 | QMultiHash _sockets; 42 | 43 | SystemdAdaptor *_dbusAdapter; 44 | 45 | int run(); 46 | int stop(); 47 | int reload(); 48 | 49 | void prepareWatchdog(); 50 | 51 | QDBusConnection dbusConnection() const; 52 | QString dbusId() const; 53 | void printDbusError(const QDBusError &error) const; 54 | 55 | static void systemdMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &message); 56 | }; 57 | 58 | Q_DECLARE_LOGGING_CATEGORY(logBackend) 59 | 60 | #endif // SYSTEMDSERVICEBACKEND_H 61 | -------------------------------------------------------------------------------- /src/plugins/servicebackends/systemd/systemdservicecontrol.h: -------------------------------------------------------------------------------- 1 | #ifndef SYSTEMDSERVICECONTROL_H 2 | #define SYSTEMDSERVICECONTROL_H 3 | 4 | #include 5 | 6 | #include 7 | 8 | class SystemdServiceControl : public QtService::ServiceControl 9 | { 10 | Q_OBJECT 11 | 12 | Q_PROPERTY(bool runAsUser READ isRunAsUser WRITE setRunAsUser RESET resetRunAsUser NOTIFY runAsUserChanged) 13 | 14 | public: 15 | explicit SystemdServiceControl(QString &&serviceId, QObject *parent = nullptr); 16 | 17 | QString backend() const override; 18 | SupportFlags supportFlags() const override; 19 | bool serviceExists() const override; 20 | Status status() const override; 21 | bool isAutostartEnabled() const override; 22 | BlockMode blocking() const override; 23 | bool isRunAsUser() const; 24 | 25 | QVariant callGenericCommand(const QByteArray &kind, const QVariantList &args) override; 26 | 27 | public Q_SLOTS: 28 | bool start() override; 29 | bool stop() override; 30 | bool restart() override; 31 | bool reload() override; 32 | bool enableAutostart() override; 33 | bool disableAutostart() override; 34 | bool setBlocking(bool blocking) override; 35 | void setRunAsUser(bool runAsUser); 36 | void resetRunAsUser(); 37 | 38 | Q_SIGNALS: 39 | void runAsUserChanged(bool runAsUser); 40 | 41 | protected: 42 | QString serviceName() const override; 43 | 44 | private: 45 | enum class SvcExists { 46 | Unknown = -1, 47 | Yes = true, 48 | No = false 49 | }; 50 | mutable SvcExists _svcInfo = SvcExists::Unknown; 51 | bool _blocking = true; 52 | bool _runAsUser; 53 | 54 | int runSystemctl(const QByteArray &command, 55 | const QStringList &extraArgs = {}, 56 | QByteArray *outData = nullptr, 57 | bool noPrepare = false) const; 58 | }; 59 | 60 | Q_DECLARE_LOGGING_CATEGORY(logControl) 61 | 62 | #endif // SYSTEMDSERVICECONTROL_H 63 | -------------------------------------------------------------------------------- /src/plugins/servicebackends/systemd/systemdserviceplugin.cpp: -------------------------------------------------------------------------------- 1 | #include "systemdserviceplugin.h" 2 | #include "systemdservicebackend.h" 3 | #include "systemdservicecontrol.h" 4 | using namespace QtService; 5 | 6 | SystemdServicePlugin::SystemdServicePlugin(QObject *parent) : 7 | QObject(parent) 8 | {} 9 | 10 | QString SystemdServicePlugin::findServiceId(const QString &backend, const QString &serviceName, const QString &domain) const 11 | { 12 | Q_UNUSED(domain) 13 | if (backend == QStringLiteral("systemd")) 14 | return serviceName + QStringLiteral(".service"); 15 | else 16 | return {}; 17 | } 18 | 19 | ServiceBackend *SystemdServicePlugin::createServiceBackend(const QString &backend, Service *service) 20 | { 21 | if (backend == QStringLiteral("systemd")) 22 | return new SystemdServiceBackend{service}; 23 | else 24 | return nullptr; 25 | } 26 | 27 | ServiceControl *SystemdServicePlugin::createServiceControl(const QString &backend, QString &&serviceId, QObject *parent) 28 | { 29 | if (backend == QStringLiteral("systemd")) 30 | return new SystemdServiceControl{std::move(serviceId), parent}; 31 | else 32 | return nullptr; 33 | } 34 | -------------------------------------------------------------------------------- /src/plugins/servicebackends/systemd/systemdserviceplugin.h: -------------------------------------------------------------------------------- 1 | #ifndef SYSTEMDSERVICEPLUGIN_H 2 | #define SYSTEMDSERVICEPLUGIN_H 3 | 4 | #include 5 | 6 | class SystemdServicePlugin : public QObject, public QtService::ServicePlugin 7 | { 8 | Q_OBJECT 9 | Q_PLUGIN_METADATA(IID QtService_ServicePlugin_Iid FILE "systemd.json") 10 | Q_INTERFACES(QtService::ServicePlugin) 11 | 12 | public: 13 | SystemdServicePlugin(QObject *parent = nullptr); 14 | 15 | QString findServiceId(const QString &backend, const QString &serviceName, const QString &domain) const override; 16 | QtService::ServiceBackend *createServiceBackend(const QString &backend, QtService::Service *service) override; 17 | QtService::ServiceControl *createServiceControl(const QString &backend, QString &&serviceId, QObject *parent) override; 18 | }; 19 | 20 | #endif // SYSTEMDSERVICEPLUGIN_H 21 | -------------------------------------------------------------------------------- /src/plugins/servicebackends/windows/windows.json: -------------------------------------------------------------------------------- 1 | { 2 | "Keys" : [ "windows" ] 3 | } 4 | -------------------------------------------------------------------------------- /src/plugins/servicebackends/windows/windows.pro: -------------------------------------------------------------------------------- 1 | TARGET = qwindows 2 | 3 | QT += service 4 | QT -= gui 5 | 6 | HEADERS += \ 7 | windowsserviceplugin.h \ 8 | windowsservicebackend.h \ 9 | windowsservicecontrol.h 10 | 11 | SOURCES += \ 12 | windowsserviceplugin.cpp \ 13 | windowsservicebackend.cpp \ 14 | windowsservicecontrol.cpp 15 | 16 | DISTFILES += windows.json 17 | 18 | LIBS += -ladvapi32 19 | 20 | PLUGIN_TYPE = servicebackends 21 | PLUGIN_EXTENDS = service 22 | PLUGIN_CLASS_NAME = WindowsServicePlugin 23 | load(qt_plugin) 24 | 25 | DISTFILES += windows.json 26 | json_target.target = $$OBJECTS_DIR/moc_windowsserviceplugin.o 27 | json_target.depends += $$PWD/windows.json 28 | QMAKE_EXTRA_TARGETS += json_target 29 | -------------------------------------------------------------------------------- /src/plugins/servicebackends/windows/windowsservicebackend.h: -------------------------------------------------------------------------------- 1 | #ifndef WINDOWSSERVICEBACKEND_H 2 | #define WINDOWSSERVICEBACKEND_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | 14 | #include 15 | 16 | class WindowsServiceBackend : public QtService::ServiceBackend 17 | { 18 | Q_OBJECT 19 | 20 | public: 21 | explicit WindowsServiceBackend(QtService::Service *service); 22 | 23 | int runService(int &argc, char **argv, int flags) override; 24 | void quitService() override; 25 | void reloadService() override; 26 | 27 | private Q_SLOTS: 28 | void onStarted(bool success); 29 | void onPaused(bool success); 30 | void onResumed(bool success); 31 | 32 | private: 33 | class SvcControlThread : public QThread 34 | { 35 | public: 36 | SvcControlThread(WindowsServiceBackend *backend); 37 | protected: 38 | void run() override; 39 | 40 | private: 41 | WindowsServiceBackend *_backend; 42 | }; 43 | 44 | class SvcEventFilter : public QAbstractNativeEventFilter 45 | { 46 | public: 47 | #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 48 | bool nativeEventFilter(const QByteArray &eventType, void *message, long *result); 49 | #else 50 | bool nativeEventFilter(const QByteArray &eventType, void *message, long long *result); 51 | #endif 52 | }; 53 | 54 | static QPointer _backendInstance; 55 | 56 | QMutex _svcLock; 57 | QWaitCondition _startCondition; 58 | 59 | SERVICE_STATUS _status; 60 | SERVICE_STATUS_HANDLE _statusHandle = nullptr; 61 | 62 | //temporary stuff 63 | QByteArrayList _svcArgs; 64 | QTimer *_opTimer = nullptr; 65 | 66 | void setStatus(DWORD status); 67 | 68 | static void WINAPI serviceMain(DWORD dwArgc, wchar_t** lpszArgv); 69 | static void WINAPI handler(DWORD dwOpcode); 70 | 71 | static void winsvcMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &message); 72 | }; 73 | 74 | Q_DECLARE_LOGGING_CATEGORY(logBackend) 75 | 76 | #endif // WINDOWSSERVICEBACKEND_H 77 | -------------------------------------------------------------------------------- /src/plugins/servicebackends/windows/windowsservicecontrol.h: -------------------------------------------------------------------------------- 1 | #ifndef WINDOWSSERVICECONTROL_H 2 | #define WINDOWSSERVICECONTROL_H 3 | 4 | #include 5 | 6 | #include 7 | 8 | #include 9 | 10 | class WindowsServiceControl : public QtService::ServiceControl 11 | { 12 | Q_OBJECT 13 | 14 | public: 15 | explicit WindowsServiceControl(QString &&serviceId, QObject *parent = nullptr); 16 | 17 | QString backend() const override; 18 | SupportFlags supportFlags() const override; 19 | bool serviceExists() const override; 20 | Status status() const override; 21 | bool isAutostartEnabled() const override; 22 | bool isEnabled() const override; 23 | BlockMode blocking() const override; 24 | QVariant callGenericCommand(const QByteArray &kind, const QVariantList &args) override; 25 | 26 | public Q_SLOTS: 27 | bool start() override; 28 | bool stop() override; 29 | bool pause() override; 30 | bool resume() override; 31 | bool enableAutostart() override; 32 | bool disableAutostart() override; 33 | bool setEnabled(bool enabled) override; 34 | 35 | private: 36 | class HandleHolder { 37 | Q_DISABLE_COPY(HandleHolder) 38 | public: 39 | HandleHolder(SC_HANDLE handle = nullptr); 40 | HandleHolder(HandleHolder &&other) noexcept; 41 | HandleHolder &operator=(SC_HANDLE handle); 42 | HandleHolder &operator=(HandleHolder &&other) noexcept; 43 | ~HandleHolder(); 44 | 45 | bool operator!() const; 46 | operator SC_HANDLE() const; 47 | private: 48 | SC_HANDLE _handle = nullptr; 49 | }; 50 | 51 | HandleHolder _manager; 52 | 53 | HandleHolder svcHandle(DWORD permissions) const; 54 | void setWinError(const QString &baseMsg) const; 55 | }; 56 | 57 | Q_DECLARE_LOGGING_CATEGORY(logControl) 58 | 59 | #endif // WINDOWSSERVICECONTROL_H 60 | -------------------------------------------------------------------------------- /src/plugins/servicebackends/windows/windowsserviceplugin.cpp: -------------------------------------------------------------------------------- 1 | #include "windowsserviceplugin.h" 2 | #include "windowsservicebackend.h" 3 | #include "windowsservicecontrol.h" 4 | 5 | WindowsServicePlugin::WindowsServicePlugin(QObject *parent) : 6 | QObject(parent) 7 | {} 8 | 9 | QString WindowsServicePlugin::findServiceId(const QString &backend, const QString &serviceName, const QString &domain) const 10 | { 11 | Q_UNUSED(domain) 12 | if (backend == QStringLiteral("windows")) 13 | return serviceName; 14 | else 15 | return {}; 16 | } 17 | 18 | QtService::ServiceBackend *WindowsServicePlugin::createServiceBackend(const QString &backend, QtService::Service *service) 19 | { 20 | if (backend == QStringLiteral("windows")) 21 | return new WindowsServiceBackend{service}; 22 | else 23 | return nullptr; 24 | } 25 | 26 | QtService::ServiceControl *WindowsServicePlugin::createServiceControl(const QString &backend, QString &&serviceId, QObject *parent) 27 | { 28 | if (backend == QStringLiteral("windows")) 29 | return new WindowsServiceControl{std::move(serviceId), parent}; 30 | else 31 | return nullptr; 32 | } 33 | -------------------------------------------------------------------------------- /src/plugins/servicebackends/windows/windowsserviceplugin.h: -------------------------------------------------------------------------------- 1 | #ifndef WINDOWSSERVICEPLUGIN_H 2 | #define WINDOWSSERVICEPLUGIN_H 3 | 4 | #include 5 | 6 | class WindowsServicePlugin : public QObject, public QtService::ServicePlugin 7 | { 8 | Q_OBJECT 9 | Q_PLUGIN_METADATA(IID QtService_ServicePlugin_Iid FILE "windows.json") 10 | Q_INTERFACES(QtService::ServicePlugin) 11 | 12 | public: 13 | WindowsServicePlugin(QObject *parent = nullptr); 14 | 15 | QString findServiceId(const QString &backend, const QString &serviceName, const QString &domain) const override; 16 | QtService::ServiceBackend *createServiceBackend(const QString &backend, QtService::Service *service) override; 17 | QtService::ServiceControl *createServiceControl(const QString &backend, QString &&serviceId, QObject *parent) override; 18 | }; 19 | 20 | #endif // WINDOWSSERVICEPLUGIN_H 21 | -------------------------------------------------------------------------------- /src/service/qtservice_global.h: -------------------------------------------------------------------------------- 1 | #ifndef QTSERVICE_GLOBAL_H 2 | #define QTSERVICE_GLOBAL_H 3 | 4 | #include 5 | 6 | #ifndef QT_STATIC 7 | # if defined(QT_BUILD_SERVICE_LIB) 8 | # define Q_SERVICE_EXPORT Q_DECL_EXPORT 9 | # else 10 | # define Q_SERVICE_EXPORT Q_DECL_IMPORT 11 | # endif 12 | #else 13 | # define Q_SERVICE_EXPORT 14 | #endif 15 | 16 | #endif // QTSERVICE_GLOBAL_H 17 | -------------------------------------------------------------------------------- /src/service/qtservice_helpertypes.h: -------------------------------------------------------------------------------- 1 | #ifndef QTSERVICE_HELPERTYPES_H 2 | #define QTSERVICE_HELPERTYPES_H 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | namespace QtService { 10 | namespace __helpertypes { 11 | 12 | template 13 | struct fn_info : public fn_info {}; 14 | 15 | template 16 | struct fn_info 17 | { 18 | template 19 | static inline std::function pack(const TFunctor &fn) { 20 | return pack(std::make_index_sequence{}, fn); 21 | } 22 | 23 | template 24 | static inline std::function pack(const std::index_sequence &, const TFunctor &fn) { 25 | return [fn](const QVariantList &args) { 26 | Q_UNUSED(args) 27 | return QVariant::fromValue(fn(args[Is].template value>()...)); 28 | }; 29 | } 30 | }; 31 | 32 | template 33 | struct fn_info 34 | { 35 | template 36 | static inline std::function pack(const TFunctor &fn) { 37 | return pack(std::make_index_sequence{}, fn); 38 | } 39 | 40 | template 41 | static inline std::function pack(const std::index_sequence &, const TFunctor &fn) { 42 | return [fn](const QVariantList &args) { 43 | Q_UNUSED(args) 44 | fn(args[Is].template value>()...); 45 | return QVariant{}; 46 | }; 47 | } 48 | }; 49 | 50 | template 51 | inline std::function pack_function(const TFunc &fn) { 52 | return fn_info::pack(fn); 53 | } 54 | 55 | } 56 | } 57 | #endif // QTSERVICE_HELPERTYPES_H 58 | -------------------------------------------------------------------------------- /src/service/service.pro: -------------------------------------------------------------------------------- 1 | TARGET = QtService 2 | 3 | QT = core network core-private 4 | android: QT += androidextras 5 | 6 | HEADERS += \ 7 | qtservice_global.h \ 8 | service.h \ 9 | service_p.h \ 10 | serviceplugin.h \ 11 | qtservice_helpertypes.h \ 12 | servicebackend.h \ 13 | servicebackend_p.h \ 14 | servicecontrol.h \ 15 | servicecontrol_p.h \ 16 | terminal.h \ 17 | terminal_p.h \ 18 | terminalserver_p.h \ 19 | terminalclient_p.h 20 | 21 | SOURCES += \ 22 | service.cpp \ 23 | servicebackend.cpp \ 24 | servicecontrol.cpp \ 25 | terminal.cpp \ 26 | terminalserver.cpp \ 27 | terminalclient.cpp \ 28 | serviceplugin.cpp 29 | 30 | MODULE_PLUGIN_TYPES = servicebackends 31 | load(qt_module) 32 | 33 | win32 { 34 | QMAKE_TARGET_PRODUCT = "QtService" 35 | QMAKE_TARGET_COMPANY = "Skycoder42" 36 | QMAKE_TARGET_COPYRIGHT = "Felix Barz" 37 | } else:mac { 38 | QMAKE_TARGET_BUNDLE_PREFIX = "de.skycoder42." 39 | } 40 | 41 | QDEP_DEPENDS += \ 42 | Skycoder42/QCtrlSignals@1.2.0 \ 43 | Skycoder42/QConsole@1.3.1 44 | 45 | !load(qdep):error("Failed to load qdep feature! Run 'qdep prfgen --qmake $$QMAKE_QMAKE' to create it.") 46 | -------------------------------------------------------------------------------- /src/service/service_p.h: -------------------------------------------------------------------------------- 1 | #ifndef QTSERVICE_SERVICE_P_H 2 | #define QTSERVICE_SERVICE_P_H 3 | 4 | #include "service.h" 5 | #include "servicebackend.h" 6 | #include "terminalserver_p.h" 7 | 8 | #include 9 | #include 10 | 11 | namespace QtService { 12 | 13 | class ServiceControl; 14 | class ServicePrivate 15 | { 16 | Q_DISABLE_COPY(ServicePrivate) 17 | public: 18 | ServicePrivate(Service *q_ptr, int &argc, char **argv, int flags); 19 | 20 | static QStringList listBackends(); 21 | static QString idFromName(const QString &provider, const QString &serviceName, const QString &domain); 22 | static ServiceControl *createControl(const QString &provider, QString &&serviceId, QObject *parent); 23 | static ServiceControl *createLocalControl(const QString &provider, QObject *parent); 24 | static QDir runtimeDir(const QString &serviceName = QCoreApplication::applicationName()); 25 | 26 | static QPointer instance; 27 | 28 | int &argc; 29 | char **argv; 30 | int flags; 31 | 32 | QString backendProvider; 33 | ServiceBackend *backend = nullptr; 34 | QHash> callbacks; 35 | 36 | bool isRunning = false; 37 | bool wasPaused = false; 38 | bool terminalActive = false; 39 | Service::TerminalMode terminalMode = Service::TerminalMode::ReadWriteActive; 40 | bool terminalGlobal = false; 41 | bool startWithTerminal = false; 42 | 43 | TerminalServer *termServer = nullptr; 44 | 45 | void startTerminals(); 46 | void stopTerminals(); 47 | 48 | private: 49 | Service *q; 50 | }; 51 | 52 | Q_DECLARE_LOGGING_CATEGORY(logSvc) 53 | 54 | } 55 | 56 | #endif // QTSERVICE_SERVICE_P_H 57 | -------------------------------------------------------------------------------- /src/service/servicebackend.h: -------------------------------------------------------------------------------- 1 | #ifndef QTSERVICE_SERVICEBACKEND_H 2 | #define QTSERVICE_SERVICEBACKEND_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "QtService/qtservice_global.h" 10 | #include "QtService/service.h" 11 | 12 | namespace QtService { 13 | 14 | class ServiceBackendPrivate; 15 | //! The interface that needs to be implemented to provide the backend for the service engine 16 | class Q_SERVICE_EXPORT ServiceBackend : public QObject 17 | { 18 | Q_OBJECT 19 | 20 | public: 21 | //! The standard service commands that the library can handle 22 | enum class ServiceCommand { 23 | Start, //!< Service was started. Will lead to Service::onStart beeing called 24 | Stop, //!< Service should stop. Will lead to Service::onStop beeing called 25 | Reload, //!< Service should reload. Will lead to Service::onReload beeing called 26 | Pause, //!< Service should pause. Will lead to Service::onPause beeing called 27 | Resume //!< Service was resumed. Will lead to Service::onResume beeing called 28 | }; 29 | Q_ENUM(ServiceCommand) 30 | 31 | //! Constructor with the service instance the backend was created for 32 | ServiceBackend(Service *service); 33 | ~ServiceBackend() override; 34 | 35 | //! Is called as the services main function by the library from Service::exec 36 | virtual int runService(int &argc, char **argv, int flags) = 0; 37 | //! Is called by Service::quit to stop the service programatically 38 | virtual void quitService() = 0; 39 | //! Is called by Service::reload to reload the service programatically 40 | virtual void reloadService() = 0; 41 | 42 | //! Is called by Service::getSockets and Service::getSocket to get the activated sockets 43 | virtual QList getActivatedSockets(const QByteArray &name); 44 | 45 | protected Q_SLOTS: 46 | //! Is called by the library if a unix signal or windows console signal was triggered 47 | virtual void signalTriggered(int signal); 48 | 49 | //! Calls the Service standard methods for the given command code 50 | void processServiceCommand(QtService::ServiceBackend::ServiceCommand code); 51 | //! Calls a special command as service callback synchronously 52 | QVariant processServiceCallbackImpl(const QByteArray &kind, const QVariantList &args = {}); 53 | 54 | protected: 55 | //! The Service instance this backend was created with 56 | QtService::Service *service() const; 57 | 58 | /*! @copybrief QtService::ServiceBackend::processServiceCallbackImpl 59 | @tparam TRet The return type 60 | @tparam TArgs Generic arguments types 61 | @copydetails QtService::ServiceBackend::processServiceCallbackImpl 62 | */ 63 | template 64 | TRet processServiceCallback(const QByteArray &kind, TArgs... args); 65 | /*! @copybrief QtService::ServiceBackend::processServiceCallbackImpl 66 | @tparam TArgs Generic arguments types 67 | @copydetails QtService::ServiceBackend::processServiceCallbackImpl 68 | */ 69 | template 70 | void processServiceCallback(const QByteArray &kind, TArgs... args); 71 | 72 | //! Register for a unix/windows signal your backend wants to handle 73 | bool registerForSignal(int signal); 74 | //! Unregister from a unix/windows signal your backend doesn't want to handle anymore 75 | bool unregisterFromSignal(int signal); 76 | 77 | //! Calls the Service::preStart method synchronously 78 | bool preStartService(); 79 | 80 | private Q_SLOTS: 81 | void onSvcStarted(bool success); 82 | void onSvcStopped(); 83 | void onSvcReloaded(bool success); 84 | void onSvcResumed(bool success); 85 | void onSvcPaused(bool success); 86 | 87 | private: 88 | QScopedPointer d; 89 | }; 90 | 91 | //! Overload for qHash 92 | Q_DECL_CONST_FUNCTION Q_DECL_CONSTEXPR inline uint qHash(QtService::ServiceBackend::ServiceCommand key, uint seed = 0) Q_DECL_NOTHROW { 93 | return static_cast(::qHash(static_cast(key), seed)); 94 | } 95 | 96 | template 97 | TRet ServiceBackend::processServiceCallback(const QByteArray &kind, TArgs... args) 98 | { 99 | return processServiceCallbackImpl(kind, {QVariant::fromValue(args)...}).template value(); 100 | } 101 | 102 | template 103 | void ServiceBackend::processServiceCallback(const QByteArray &kind, TArgs... args) 104 | { 105 | processServiceCallbackImpl(kind, {QVariant::fromValue(args)...}); 106 | } 107 | 108 | } 109 | 110 | Q_DECLARE_METATYPE(QtService::ServiceBackend::ServiceCommand) 111 | 112 | #endif // QTSERVICE_SERVICEBACKEND_H 113 | -------------------------------------------------------------------------------- /src/service/servicebackend_p.h: -------------------------------------------------------------------------------- 1 | #ifndef QTSERVICE_SERVICEBACKEND_P_H 2 | #define QTSERVICE_SERVICEBACKEND_P_H 3 | 4 | #include "servicebackend.h" 5 | 6 | #include 7 | 8 | namespace QtService { 9 | 10 | class ServiceBackendPrivate 11 | { 12 | Q_DISABLE_COPY(ServiceBackendPrivate) 13 | public: 14 | ServiceBackendPrivate(Service *service); 15 | 16 | Service *service; 17 | bool operating = false; 18 | }; 19 | 20 | Q_DECLARE_LOGGING_CATEGORY(logBackend) // MAJOR make virtual in public part 21 | 22 | } 23 | 24 | #endif // QTSERVICE_SERVICEBACKEND_P_H 25 | -------------------------------------------------------------------------------- /src/service/servicecontrol_p.h: -------------------------------------------------------------------------------- 1 | #ifndef QTSERVICE_SERVICECONTROL_P_H 2 | #define QTSERVICE_SERVICECONTROL_P_H 3 | 4 | #include "qtservice_global.h" 5 | #include "servicecontrol.h" 6 | 7 | #include 8 | 9 | namespace QtService { 10 | 11 | class ServiceControlPrivate 12 | { 13 | Q_DISABLE_COPY(ServiceControlPrivate) 14 | 15 | public: 16 | ServiceControlPrivate(QString &&serviceId); 17 | 18 | QString serviceId; 19 | QString serviceName; 20 | bool blocking = true; 21 | QString error; 22 | }; 23 | 24 | Q_DECLARE_LOGGING_CATEGORY(logSvcCtrl) 25 | 26 | } 27 | 28 | #endif // QTSERVICE_SERVICECONTROL_P_H 29 | -------------------------------------------------------------------------------- /src/service/serviceplugin.cpp: -------------------------------------------------------------------------------- 1 | #include "serviceplugin.h" 2 | #include 3 | #include 4 | 5 | QtService::ServicePlugin::ServicePlugin() = default; 6 | 7 | QtService::ServicePlugin::~ServicePlugin() = default; 8 | 9 | QString QtService::ServicePlugin::currentServiceId(const QString &backend) const 10 | { 11 | return findServiceId(backend, QCoreApplication::applicationName(), QCoreApplication::organizationDomain()); 12 | } 13 | -------------------------------------------------------------------------------- /src/service/serviceplugin.h: -------------------------------------------------------------------------------- 1 | #ifndef QTSERVICE_SERVICEPLUGIN_H 2 | #define QTSERVICE_SERVICEPLUGIN_H 3 | 4 | #include 5 | 6 | #include "QtService/qtservice_global.h" 7 | 8 | namespace QtService { 9 | 10 | class Service; 11 | class ServiceBackend; 12 | class ServiceControl; 13 | //! The plugin interface to implement as primary interface of a servicebackend plugin 14 | class Q_SERVICE_EXPORT ServicePlugin 15 | { 16 | Q_DISABLE_COPY(ServicePlugin) 17 | 18 | public: 19 | ServicePlugin(); 20 | virtual ~ServicePlugin(); 21 | 22 | //! Return the ID of the currently setup service 23 | virtual QString currentServiceId(const QString &backend) const; 24 | //! Guess the ID of a service that has the given name within the given domain 25 | virtual QString findServiceId(const QString &backend, const QString &serviceName, const QString &domain) const = 0; 26 | 27 | //! Create a new service backend for the given backend and service 28 | virtual ServiceBackend *createServiceBackend(const QString &backend, Service *service) = 0; 29 | //! Create a new service backend for the given backend, name and parent 30 | virtual ServiceControl *createServiceControl(const QString &backend, QString &&serviceId, QObject *parent) = 0; 31 | }; 32 | 33 | } 34 | 35 | //! The IID to be used to create a service plugin 36 | #define QtService_ServicePlugin_Iid "de.skycoder42.QtService.ServicePlugin" 37 | Q_DECLARE_INTERFACE(QtService::ServicePlugin, QtService_ServicePlugin_Iid) 38 | 39 | //! @file serviceplugin.h The ServicePlugin header 40 | #endif // QTSERVICE_SERVICEPLUGIN_H 41 | -------------------------------------------------------------------------------- /src/service/terminal.h: -------------------------------------------------------------------------------- 1 | #ifndef QTSERVICE_TERMINAL_H 2 | #define QTSERVICE_TERMINAL_H 3 | 4 | #include 5 | #include 6 | 7 | #include "QtService/qtservice_global.h" 8 | #include "QtService/service.h" 9 | 10 | namespace QtService { 11 | 12 | class TerminalPrivate; 13 | class TerminalAwaitablePrivate; 14 | //! Represents a connection to a console terminal connected to the service 15 | class Q_SERVICE_EXPORT Terminal : public QIODevice 16 | { 17 | Q_OBJECT 18 | 19 | //! The I/O-mode the terminal operates in 20 | Q_PROPERTY(QtService::Service::TerminalMode terminalMode READ terminalMode CONSTANT) 21 | //! The command line arguments that have been used to create this terminal 22 | Q_PROPERTY(QStringList command READ command CONSTANT) 23 | //! If true, the terminal will delete itself as soon as the connection has been closed 24 | Q_PROPERTY(bool autoDelete READ isAutoDelete WRITE setAutoDelete NOTIFY autoDeleteChanged) 25 | 26 | public: 27 | //! A helper class to be used with [QtCoroutines](https://github.com/Skycoder42/QtCoroutines) to await io from a coroutine 28 | class Q_SERVICE_EXPORT Awaitable 29 | { 30 | public: 31 | //! Special read modes 32 | enum SpecialReads : qint64 { 33 | ReadLine = 0, //!< Read until the next newline (QIODevice::readLine) 34 | ReadSingle = 1 //!< Read only a single character 35 | }; 36 | 37 | //! Create an awaitable for the given terminal and specify how much data should be read 38 | Awaitable(Terminal *terminal, qint64 readCnt = ReadSingle); 39 | //! Move constructor 40 | Awaitable(Awaitable &&other) noexcept; 41 | //! Move assignment operator 42 | Awaitable &operator=(Awaitable &&other) noexcept; 43 | ~Awaitable(); 44 | 45 | //! @private 46 | using type = QByteArray; 47 | //! @private 48 | void prepare(std::function resume); 49 | //! @private 50 | type result(); 51 | 52 | private: 53 | QScopedPointer d; 54 | }; 55 | 56 | //! @private 57 | explicit Terminal(TerminalPrivate *d_ptr, QObject *parent = nullptr); 58 | ~Terminal() override; 59 | 60 | //! @inherit{QIODevice::isSequential} 61 | bool isSequential() const override; 62 | //! @inherit{QIODevice::close} 63 | void close() override; 64 | //! @inherit{QIODevice::atEnd} 65 | bool atEnd() const override; 66 | //! @inherit{QIODevice::bytesAvailable} 67 | qint64 bytesAvailable() const override; 68 | //! @inherit{QIODevice::bytesToWrite} 69 | qint64 bytesToWrite() const override; 70 | //! @inherit{QIODevice::canReadLine} 71 | bool canReadLine() const override; 72 | //! @inherit{QIODevice::waitForReadyRead} 73 | bool waitForReadyRead(int msecs) override; 74 | //! @inherit{QIODevice::waitForBytesWritten} 75 | bool waitForBytesWritten(int msecs) override; 76 | 77 | //! @readAcFn{Terminal::terminalMode} 78 | Service::TerminalMode terminalMode() const; 79 | //! @readAcFn{Terminal::command} 80 | QStringList command() const; 81 | //! @readAcFn{Terminal::autoDelete} 82 | bool isAutoDelete() const; 83 | 84 | //awaitables 85 | //! Await a single character 86 | Awaitable awaitChar(); 87 | //! Await a given number of characters 88 | Awaitable awaitChars(qint64 num); 89 | //! Await a line of characters 90 | Awaitable awaitLine(); 91 | 92 | public Q_SLOTS: 93 | //! Disconnects the terminal from the client 94 | void disconnectTerminal(); 95 | 96 | //! Request a single character from the terminal 97 | void requestChar(); 98 | //! Request a given number of charactersr from the terminal 99 | void requestChars(qint64 num); 100 | //! Request a line of characters from the terminal 101 | void requestLine(); 102 | 103 | //! Writes the given line, appends a newline and optionally flushes 104 | void writeLine(const QByteArray &line, bool flush = true); 105 | //! Flushes the terminal 106 | void flush(); 107 | 108 | //! @writeAcFn{Terminal::autoDelete} 109 | void setAutoDelete(bool autoDelete); 110 | 111 | Q_SIGNALS: 112 | //! Will be emitted after the terminal has been disconnected 113 | void terminalDisconnected(); 114 | //! Will be emitted if an error occured. Use QIODevice::errorString to get the text 115 | void terminalError(int errorCode); 116 | 117 | //! @notifyAcFn{Terminal::autoDelete} 118 | void autoDeleteChanged(bool autoDelete); 119 | 120 | protected: 121 | //! @inherit{QIODevice::readData} 122 | qint64 readData(char *data, qint64 maxlen) override; 123 | //! @inherit{QIODevice::readLineData} 124 | qint64 readLineData(char *data, qint64 maxlen) override; 125 | //! @inherit{QIODevice::writeData} 126 | qint64 writeData(const char *data, qint64 len) override; 127 | 128 | private: 129 | TerminalPrivate *d; 130 | 131 | bool open(OpenMode mode) override; 132 | }; 133 | 134 | } 135 | 136 | #endif // QTSERVICE_TERMINAL_H 137 | -------------------------------------------------------------------------------- /src/service/terminal_p.h: -------------------------------------------------------------------------------- 1 | #ifndef QTSERVICE_TERMINAL_P_H 2 | #define QTSERVICE_TERMINAL_P_H 3 | 4 | #include "qtservice_global.h" 5 | #include "terminal.h" 6 | 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | namespace QtService { 13 | 14 | // exported as generally terminals are created from their private component 15 | class Q_SERVICE_EXPORT TerminalPrivate : public QObject 16 | { 17 | Q_OBJECT 18 | friend class QtService::Terminal; 19 | 20 | public: 21 | enum RequestType { 22 | InvalidRequest = 0, 23 | 24 | CharRequest = 1, 25 | MultiCharRequest = 2, 26 | LineRequest = 3 27 | }; 28 | Q_ENUM(RequestType) 29 | 30 | TerminalPrivate(QLocalSocket *socket, QObject *parent = nullptr); 31 | 32 | Q_SIGNALS: 33 | void terminalReady(TerminalPrivate *terminal, bool successful); 34 | 35 | private Q_SLOTS: 36 | void disconnected(); 37 | void error(); 38 | void readyRead(); 39 | 40 | private: 41 | QLocalSocket *socket; 42 | 43 | Service::TerminalMode terminalMode = Service::TerminalMode::ReadWriteActive; 44 | QStringList command; 45 | bool autoDelete = true; 46 | 47 | bool isLoading = true; 48 | QDataStream commandStream; 49 | }; 50 | 51 | class TerminalAwaitablePrivate 52 | { 53 | Q_DISABLE_COPY(TerminalAwaitablePrivate) 54 | public: 55 | TerminalAwaitablePrivate(Terminal *terminal, qint64 readCnt); 56 | 57 | Terminal * const terminal; 58 | const qint64 readCnt; 59 | QMetaObject::Connection connection; 60 | QByteArray result; 61 | }; 62 | 63 | Q_DECLARE_LOGGING_CATEGORY(logTerm) 64 | 65 | } 66 | 67 | #endif // QTSERVICE_TERMINAL_P_H 68 | -------------------------------------------------------------------------------- /src/service/terminalclient_p.h: -------------------------------------------------------------------------------- 1 | #ifndef QTSERVICE_TERMINALCLIENT_P_H 2 | #define QTSERVICE_TERMINALCLIENT_P_H 3 | 4 | #include "qtservice_global.h" 5 | #include "service.h" 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | class QConsole; 14 | 15 | namespace QtService { 16 | 17 | class TerminalClient : public QObject 18 | { 19 | Q_OBJECT 20 | 21 | public: 22 | explicit TerminalClient(Service *service); 23 | ~TerminalClient() override; 24 | 25 | int exec(int &argc, char **argv, int flags); 26 | 27 | private Q_SLOTS: 28 | void doConnect(); 29 | 30 | void connected(); 31 | void disconnected(); 32 | void error(QLocalSocket::LocalSocketError socketError); 33 | void socketReady(); 34 | 35 | void consoleReady(); 36 | 37 | private: 38 | Service *_service; 39 | QStringList _cmdArgs; 40 | 41 | Service::TerminalMode _mode = Service::TerminalMode::ReadWriteActive; 42 | QLocalSocket *_socket = nullptr; 43 | QDataStream _stream; 44 | QFile *_outFile = nullptr; 45 | 46 | QFile *_inFile = nullptr; 47 | QConsole *_inConsole = nullptr; 48 | 49 | bool _exitFailed = false; 50 | 51 | bool verifyArgs(); 52 | bool ensureServiceStarted(); 53 | void setupChannels(); 54 | 55 | static void cerrMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &message); 56 | }; 57 | 58 | Q_DECLARE_LOGGING_CATEGORY(logTermClient) 59 | 60 | } 61 | 62 | #endif // QTSERVICE_TERMINALCLIENT_P_H 63 | -------------------------------------------------------------------------------- /src/service/terminalserver.cpp: -------------------------------------------------------------------------------- 1 | #include "terminalserver_p.h" 2 | #include "terminal_p.h" 3 | #include "service_p.h" 4 | using namespace QtService; 5 | 6 | Q_LOGGING_CATEGORY(QtService::logTermServer, "qt.service.terminal.server") 7 | 8 | TerminalServer::TerminalServer(Service *service) : 9 | QObject{service}, 10 | _service{service}, 11 | _server{new QLocalServer{this}} 12 | { 13 | connect(_server, &QLocalServer::newConnection, 14 | this, &TerminalServer::newConnection); 15 | } 16 | 17 | QString TerminalServer::serverName() 18 | { 19 | #ifdef Q_OS_WIN 20 | return QStringLiteral(R"__(\\.\pipe\de.skycoder42.QtService.%1.terminal)__") 21 | .arg(QCoreApplication::applicationName()); 22 | #else 23 | return ServicePrivate::runtimeDir().absoluteFilePath(QStringLiteral("terminal.socket")); 24 | #endif 25 | } 26 | 27 | bool TerminalServer::start(bool globally) 28 | { 29 | _server->setSocketOptions(globally ? QLocalServer::WorldAccessOption : QLocalServer::UserAccessOption); 30 | auto activeSockets = _service->getSockets("terminal"); 31 | if (activeSockets.isEmpty()) { 32 | auto name = serverName(); 33 | if (!_server->listen(name)) { 34 | if (_server->serverError() == QAbstractSocket::AddressInUseError) { 35 | if (QLocalServer::removeServer(name)) 36 | _server->listen(name); 37 | } 38 | } 39 | } else { 40 | if (_activated) 41 | qCWarning(logTermServer) << "Reopening an already closed activated socket is not supported and will result in undefined behaviour!"; 42 | if (activeSockets.size() > 1) 43 | qCWarning(logTermServer) << "Found more then 1 activated terminal socket - using first one:" << activeSockets.first(); 44 | _activated = _server->listen(activeSockets.first()) || _activated; 45 | } 46 | 47 | if (_server->isListening()) 48 | return true; 49 | else { 50 | qCCritical(logTermServer) << "Failed to create terminal server with error:" << _server->errorString(); 51 | return false; 52 | } 53 | } 54 | 55 | void TerminalServer::stop() 56 | { 57 | _server->close(); 58 | } 59 | 60 | bool TerminalServer::isRunning() const 61 | { 62 | return _server->isListening(); 63 | } 64 | 65 | void TerminalServer::newConnection() 66 | { 67 | while (_server->hasPendingConnections()) { 68 | auto terminal = new TerminalPrivate { 69 | _server->nextPendingConnection(), 70 | this 71 | }; 72 | connect(terminal, &TerminalPrivate::terminalReady, 73 | this, &TerminalServer::terminalReady); 74 | } 75 | } 76 | 77 | void TerminalServer::terminalReady(TerminalPrivate *terminal, bool success) 78 | { 79 | if (success) 80 | emit terminalConnected(new Terminal{terminal, _service}); 81 | else 82 | terminal->deleteLater(); 83 | } 84 | -------------------------------------------------------------------------------- /src/service/terminalserver_p.h: -------------------------------------------------------------------------------- 1 | #ifndef QTSERVICE_TERMINALSERVER_P_H 2 | #define QTSERVICE_TERMINALSERVER_P_H 3 | 4 | #include "terminal.h" 5 | #include "service.h" 6 | 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | namespace QtService { 13 | 14 | class TerminalPrivate; 15 | class TerminalServer : public QObject 16 | { 17 | Q_OBJECT 18 | 19 | public: 20 | explicit TerminalServer(Service *service); 21 | 22 | static QString serverName(); 23 | 24 | bool start(bool globally); 25 | void stop(); 26 | 27 | bool isRunning() const; 28 | 29 | Q_SIGNALS: 30 | void terminalConnected(QtService::Terminal *terminal); 31 | 32 | private Q_SLOTS: 33 | void newConnection(); 34 | 35 | void terminalReady(TerminalPrivate *terminal, bool success); 36 | 37 | private: 38 | Service *_service; 39 | QLocalServer *_server; 40 | bool _activated = false; 41 | 42 | bool setSocketDescriptor(int socket); 43 | }; 44 | 45 | Q_DECLARE_LOGGING_CATEGORY(logTermServer) 46 | 47 | } 48 | 49 | #endif // QTSERVICE_TERMINALSERVER_P_H 50 | -------------------------------------------------------------------------------- /src/src.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = subdirs 2 | 3 | SUBDIRS += service \ 4 | plugins \ 5 | imports \ 6 | translations 7 | 8 | android:!android-embedded: SUBDIRS += java 9 | 10 | plugins.depends += service 11 | imports.depends += service 12 | 13 | QMAKE_EXTRA_TARGETS += run-tests 14 | 15 | lupdate.target = lupdate 16 | lupdate.CONFIG = recursive 17 | lupdate.recurse_target = lupdate 18 | lupdate.recurse += translations 19 | QMAKE_EXTRA_TARGETS += lupdate 20 | -------------------------------------------------------------------------------- /src/translations/translations.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = aux 2 | 3 | QDEP_LUPDATE_INPUTS += $$PWD/../service 4 | QDEP_LUPDATE_INPUTS += $$PWD/../plugins 5 | QDEP_LUPDATE_INPUTS += $$PWD/../imports 6 | QDEP_LUPDATE_INPUTS += $$PWD/../java 7 | 8 | TRANSLATIONS += \ 9 | qtservice_de.ts \ 10 | qtservice_template.ts 11 | 12 | CONFIG += lrelease 13 | QM_FILES_INSTALL_PATH = $$[QT_INSTALL_TRANSLATIONS] 14 | 15 | QDEP_DEPENDS += \ 16 | Skycoder42/QCtrlSignals@1.2.0 \ 17 | Skycoder42/QConsole@1.3.1 18 | 19 | !load(qdep):error("Failed to load qdep feature! Run 'qdep prfgen --qmake $$QMAKE_QMAKE' to create it.") 20 | 21 | #replace template qm by ts 22 | QM_FILES -= $$__qdep_lrelease_real_dir/qtservice_template.qm 23 | QM_FILES += qtservice_template.ts 24 | 25 | HEADERS = 26 | SOURCES = 27 | GENERATED_SOURCES = 28 | OBJECTIVE_SOURCES = 29 | RESOURCES = 30 | -------------------------------------------------------------------------------- /sync.profile: -------------------------------------------------------------------------------- 1 | %modules = ( 2 | "QtService" => "$basedir/src/service", 3 | ); 4 | 5 | # Force generation of camel case headers for classes inside QtDataSync namespaces 6 | $publicclassregexp = "QtService::(?!__helpertypes).+"; 7 | -------------------------------------------------------------------------------- /tests/auto/auto.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = subdirs 2 | 3 | SUBDIRS += cmake \ 4 | service 5 | 6 | cmake.CONFIG += no_run-tests_target 7 | prepareRecursiveTarget(run-tests) 8 | QMAKE_EXTRA_TARGETS += run-tests 9 | -------------------------------------------------------------------------------- /tests/auto/cmake/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | cmake_minimum_required(VERSION 2.8) 3 | 4 | project(qmake_cmake_files) 5 | 6 | enable_testing() 7 | 8 | find_package(Qt5Core REQUIRED) 9 | 10 | include("${_Qt5CTestMacros}") 11 | 12 | test_module_includes( 13 | Service QService 14 | ) 15 | -------------------------------------------------------------------------------- /tests/auto/cmake/cmake.pro: -------------------------------------------------------------------------------- 1 | 2 | # Cause make to do nothing. 3 | TEMPLATE = subdirs 4 | 5 | CMAKE_QT_MODULES_UNDER_TEST = service 6 | 7 | CONFIG += ctest_testcase 8 | -------------------------------------------------------------------------------- /tests/auto/service/TestBaseLib/TestBaseLib.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = lib 2 | CONFIG += static 3 | 4 | QT = core service testlib 5 | 6 | CONFIG += console 7 | CONFIG -= app_bundle 8 | 9 | DEFINES += SRCDIR=\\\"$$_PRO_FILE_PWD_/\\\" 10 | 11 | TARGET = testbase 12 | 13 | HEADERS += \ 14 | basicservicetest.h 15 | 16 | SOURCES += \ 17 | basicservicetest.cpp 18 | 19 | runtarget.target = run-tests 20 | !compat_test { 21 | win32: runtarget.depends += $(DESTDIR_TARGET) 22 | else: runtarget.depends += $(TARGET) 23 | } 24 | QMAKE_EXTRA_TARGETS += runtarget 25 | -------------------------------------------------------------------------------- /tests/auto/service/TestBaseLib/basicservicetest.h: -------------------------------------------------------------------------------- 1 | #ifndef BASICSERVICETEST_H 2 | #define BASICSERVICETEST_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | class BasicServiceTest : public QObject 10 | { 11 | Q_OBJECT 12 | 13 | public: 14 | explicit BasicServiceTest(QObject *parent = nullptr); 15 | 16 | private Q_SLOTS: 17 | void initTestCase(); 18 | void cleanupTestCase(); 19 | 20 | void testBasics(); 21 | void testNameDetection(); 22 | 23 | void testStart(); 24 | void testReload(); 25 | void testPause(); 26 | void testResume(); 27 | void testRestart(); 28 | void testCustom(); 29 | void testStop(); 30 | 31 | #ifndef Q_OS_WIN 32 | void testStartExit(); 33 | void testStartFail(); 34 | #endif 35 | 36 | void testAutostart(); 37 | void testDisable(); 38 | 39 | protected: 40 | QtService::ServiceControl *control = nullptr; 41 | QLocalSocket *socket = nullptr; 42 | QDataStream stream; 43 | 44 | virtual QString backend() = 0; 45 | virtual QString name(); 46 | virtual bool reportsStartErrors(); 47 | virtual void init(); 48 | virtual void cleanup(); 49 | virtual bool resetFailed(); 50 | 51 | virtual void testCustomImpl(); 52 | 53 | void resetSettings(const QVariantHash &args = {}); 54 | void performSocketTest(); 55 | void testFeature(QtService::ServiceControl::SupportFlag flag); 56 | void waitUntil(QtService::ServiceControl::Status status); 57 | }; 58 | 59 | #define READ_LOOP(...) do { \ 60 | QVERIFY(socket->waitForReadyRead(30000)); \ 61 | stream.startTransaction(); \ 62 | stream >> __VA_ARGS__; \ 63 | } while(!stream.commitTransaction()) 64 | 65 | #define TEST_STATUS(state) do {\ 66 | if(control->supportFlags().testFlag(ServiceControl::SupportFlag::Status)) { \ 67 | waitUntil(state); \ 68 | QCOMPARE(control->status(), state); \ 69 | } \ 70 | } while(false) 71 | 72 | #endif // BASICSERVICETEST_H 73 | -------------------------------------------------------------------------------- /tests/auto/service/TestLaunchdService/TestLaunchdService.pro: -------------------------------------------------------------------------------- 1 | include(../testlib.pri) 2 | 3 | TARGET = tst_launchdservice 4 | 5 | SOURCES += \ 6 | tst_launchdservice.cpp 7 | 8 | DISTFILES += \ 9 | de.skycoder42.qtservice.tests.testservice.plist 10 | -------------------------------------------------------------------------------- /tests/auto/service/TestLaunchdService/de.skycoder42.qtservice.tests.testservice.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Label 6 | de.skycoder42.qtservice.tests.testservice 7 | ProgramArguments 8 | 9 | %{TESTSERVICE_PATH} 10 | --backend 11 | launchd 12 | 13 | EnvironmentVariables 14 | 15 | DYLD_LIBRARY_PATH 16 | %{DYLD_LIBRARY_PATH} 17 | DYLD_FRAMEWORK_PATH 18 | %{DYLD_FRAMEWORK_PATH} 19 | QT_PLUGIN_PATH 20 | %{QT_PLUGIN_PATH} 21 | 22 | Sockets 23 | 24 | Listeners 25 | 26 | SockFamily 27 | IPv4 28 | SockServiceName 29 | 15843 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /tests/auto/service/TestLaunchdService/tst_launchdservice.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #define launchdpath QStringLiteral("Library/LaunchAgents") 7 | #define testservice QStringLiteral("de.skycoder42.qtservice.tests.testservice.plist") 8 | 9 | class TestLaunchdService : public BasicServiceTest 10 | { 11 | Q_OBJECT 12 | 13 | protected: 14 | QString backend() override; 15 | QString name() override; 16 | void init() override; 17 | void cleanup() override; 18 | void testCustomImpl() override; 19 | 20 | private Q_SLOTS: 21 | void testSocketActivation(); 22 | 23 | private: 24 | bool launchdLoad(const QString &svc, bool load); 25 | }; 26 | 27 | QString TestLaunchdService::backend() 28 | { 29 | return QStringLiteral("launchd"); 30 | } 31 | 32 | QString TestLaunchdService::name() 33 | { 34 | return QStringLiteral("de.skycoder42.qtservice.tests.testservice"); 35 | } 36 | 37 | void TestLaunchdService::init() 38 | { 39 | QDir srcDir(QStringLiteral(SRCDIR)); 40 | QVERIFY(srcDir.exists()); 41 | QVERIFY(srcDir.exists(testservice)); 42 | 43 | auto launchdHome = QDir{QStandardPaths::writableLocation(QStandardPaths::HomeLocation)}; 44 | QVERIFY(launchdHome.mkpath(launchdpath)); 45 | QVERIFY(launchdHome.cd(launchdpath)); 46 | 47 | if(!launchdHome.exists(testservice)) { 48 | QFile in{srcDir.absoluteFilePath(testservice)}; 49 | QVERIFY(in.open(QIODevice::ReadOnly | QIODevice::Text)); 50 | QFile out{launchdHome.absoluteFilePath(testservice)}; 51 | QVERIFY(out.open(QIODevice::WriteOnly | QIODevice::Text)); 52 | out.write(in.readAll() 53 | .replace("%{DYLD_LIBRARY_PATH}", qgetenv("DYLD_LIBRARY_PATH")) 54 | .replace("%{DYLD_FRAMEWORK_PATH}", qgetenv("DYLD_FRAMEWORK_PATH")) 55 | .replace("%{QT_PLUGIN_PATH}", qgetenv("QT_PLUGIN_PATH")) 56 | .replace("%{TESTSERVICE_PATH}", QString(QCoreApplication::applicationDirPath() + QStringLiteral("/../TestService/testservice")).toUtf8())); 57 | } 58 | 59 | QVERIFY(launchdLoad(launchdHome.absoluteFilePath(testservice), true)); 60 | } 61 | 62 | void TestLaunchdService::cleanup() 63 | { 64 | auto launchdHome = QDir{QStandardPaths::writableLocation(QStandardPaths::HomeLocation)}; 65 | QVERIFY(launchdHome.cd(launchdpath)); 66 | launchdLoad(launchdHome.absoluteFilePath(testservice), false); 67 | QVERIFY(launchdHome.remove(testservice)); 68 | } 69 | 70 | void TestLaunchdService::testCustomImpl() 71 | { 72 | QCOMPARE(control->callCommand("list"), EXIT_SUCCESS); 73 | } 74 | 75 | void TestLaunchdService::testSocketActivation() 76 | { 77 | auto launchdHome = QDir{QStandardPaths::writableLocation(QStandardPaths::HomeLocation)}; 78 | QVERIFY(launchdLoad(launchdHome.absoluteFilePath(testservice), false)); 79 | QVERIFY(launchdLoad(launchdHome.absoluteFilePath(testservice), true)); 80 | performSocketTest(); 81 | } 82 | 83 | bool TestLaunchdService::launchdLoad(const QString &svc, bool load) 84 | { 85 | QStringList args { 86 | load ? QStringLiteral("load") : QStringLiteral("unload"), 87 | svc 88 | }; 89 | return QProcess::execute(QStringLiteral("launchctl"), args) == EXIT_SUCCESS; 90 | } 91 | 92 | QTEST_MAIN(TestLaunchdService) 93 | 94 | #include "tst_launchdservice.moc" 95 | -------------------------------------------------------------------------------- /tests/auto/service/TestService/TestService.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = app 2 | 3 | QT = core service 4 | 5 | CONFIG += console 6 | CONFIG -= app_bundle 7 | 8 | TARGET = testservice 9 | 10 | HEADERS += \ 11 | testservice.h 12 | 13 | SOURCES += \ 14 | main.cpp \ 15 | testservice.cpp 16 | 17 | runtarget.target = run-tests 18 | !compat_test { 19 | win32: runtarget.depends += $(DESTDIR_TARGET) 20 | else: runtarget.depends += $(TARGET) 21 | } 22 | QMAKE_EXTRA_TARGETS += runtarget 23 | -------------------------------------------------------------------------------- /tests/auto/service/TestService/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "testservice.h" 3 | 4 | int main(int argc, char *argv[]) 5 | { 6 | #ifdef Q_CC_MSVC 7 | // WORKAROUND for windows service not beeing able to pass env vars 8 | if (qEnvironmentVariable("QT_PLUGIN_PATH").isEmpty()) { 9 | qputenv("QT_PLUGIN_PATH", QFileInfo{QString::fromUtf8(argv[0])}.dir().absolutePath().toUtf8()); 10 | qDebug() << "QT_PLUGIN_PATH" << qEnvironmentVariable("QT_PLUGIN_PATH"); 11 | } 12 | if (qEnvironmentVariable("QT_LOGGING_RULES").isEmpty()) { 13 | qputenv("QT_LOGGING_RULES", "qt.service.*.debug=true"); 14 | qDebug() << "QT_LOGGING_RULES" << qEnvironmentVariable("QT_LOGGING_RULES"); 15 | } 16 | #endif 17 | qDebug() << "libraryPaths" << QCoreApplication::libraryPaths(); 18 | 19 | TestService service{argc, argv}; 20 | QCoreApplication::setApplicationName(QStringLiteral("testservice")); 21 | QCoreApplication::setApplicationVersion(QStringLiteral("1.0.0")); 22 | QCoreApplication::setOrganizationDomain(QStringLiteral("de.skycoder42.qtservice.tests")); 23 | return service.exec(); 24 | } 25 | -------------------------------------------------------------------------------- /tests/auto/service/TestService/testservice.cpp: -------------------------------------------------------------------------------- 1 | #include "testservice.h" 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | using namespace QtService; 8 | 9 | TestService::TestService(int &argc, char **argv) : 10 | Service{argc, argv} 11 | { 12 | setTerminalActive(true); 13 | setStartWithTerminal(true); 14 | } 15 | 16 | bool TestService::preStart() 17 | { 18 | qDebug() << Q_FUNC_INFO; 19 | return true; 20 | } 21 | 22 | Service::CommandResult TestService::onStart() 23 | { 24 | qDebug() << Q_FUNC_INFO; 25 | 26 | //first: read mode of operation: 27 | #ifndef Q_OS_WIN 28 | QSettings config{runtimeDir().absoluteFilePath(QStringLiteral("test.conf")), QSettings::IniFormat}; 29 | if(config.value(QStringLiteral("exit")).toBool()) { 30 | qDebug() << "Exiting in onStart operation"; 31 | return CommandResult::Exit; 32 | } if(config.value(QStringLiteral("fail")).toBool()) { 33 | qDebug() << "Failing onStart operation"; 34 | return CommandResult::Failed; 35 | } 36 | #endif 37 | 38 | _server = new QLocalServer(this); 39 | _server->setSocketOptions(QLocalServer::WorldAccessOption); 40 | connect(_server, &QLocalServer::newConnection, this, [this](){ 41 | qDebug() << "new connection"; 42 | if(_socket) 43 | return; 44 | _socket = _server->nextPendingConnection(); 45 | _stream.setDevice(_socket); 46 | _server->close(); 47 | 48 | _stream << QByteArray("started"); 49 | _socket->flush(); 50 | }); 51 | _server->listen(QStringLiteral("__qtservice_testservice")); 52 | qDebug() << "listening:" << _server->isListening(); 53 | 54 | //also: start basic TCP server if applicable 55 | auto socket = getSocket(); 56 | if(socket >= 0) { 57 | _activatedServer = new QTcpServer(this); 58 | connect(_activatedServer, &QTcpServer::newConnection, 59 | this, [this]() { 60 | while(_activatedServer->hasPendingConnections()) { 61 | auto tcpSocket = _activatedServer->nextPendingConnection(); 62 | tcpSocket->setParent(this); 63 | connect(tcpSocket, &QTcpSocket::readyRead, 64 | tcpSocket, [tcpSocket](){ 65 | tcpSocket->write(tcpSocket->readAll()); 66 | }); 67 | } 68 | }); 69 | _activatedServer->setSocketDescriptor(socket); 70 | } 71 | 72 | qDebug() << "start ready"; 73 | return CommandResult::Completed; 74 | } 75 | 76 | Service::CommandResult TestService::onStop(int &exitCode) 77 | { 78 | Q_UNUSED(exitCode); 79 | qDebug() << Q_FUNC_INFO; 80 | _stream << QByteArray("stopping"); 81 | if(_socket) { 82 | _socket->flush(); 83 | _socket->waitForBytesWritten(2500); 84 | } 85 | return CommandResult::Completed; 86 | } 87 | 88 | Service::CommandResult TestService::onReload() 89 | { 90 | qDebug() << Q_FUNC_INFO; 91 | 92 | #ifndef Q_OS_WIN 93 | QSettings config{runtimeDir().absoluteFilePath(QStringLiteral("test.conf")), QSettings::IniFormat}; 94 | if(config.value(QStringLiteral("fail")).toBool()) { 95 | qDebug() << "Failing onReload operation"; 96 | return CommandResult::Failed; 97 | } 98 | #endif 99 | 100 | _stream << QByteArray("reloading"); 101 | _socket->flush(); 102 | return CommandResult::Completed; 103 | } 104 | 105 | Service::CommandResult TestService::onPause() 106 | { 107 | qDebug() << Q_FUNC_INFO; 108 | _stream << QByteArray("pausing"); 109 | _socket->flush(); 110 | _socket->waitForBytesWritten(2500); 111 | return CommandResult::Completed; 112 | } 113 | 114 | Service::CommandResult TestService::onResume() 115 | { 116 | qDebug() << Q_FUNC_INFO; 117 | _stream << QByteArray("resuming"); 118 | _socket->flush(); 119 | return CommandResult::Completed; 120 | } 121 | 122 | QVariant TestService::onCallback(const QByteArray &kind, const QVariantList &args) 123 | { 124 | qDebug() << Q_FUNC_INFO << kind << args; 125 | _stream << kind << args; 126 | _socket->flush(); 127 | return true; 128 | } 129 | 130 | bool TestService::verifyCommand(const QStringList &arguments) 131 | { 132 | qDebug() << Q_FUNC_INFO << arguments; 133 | if(arguments.contains(QStringLiteral("--passive"))) 134 | setTerminalMode(Service::TerminalMode::ReadWritePassive); 135 | else 136 | setTerminalMode(Service::TerminalMode::ReadWriteActive); 137 | return true; 138 | } 139 | 140 | void TestService::terminalConnected(Terminal *terminal) 141 | { 142 | qDebug() << Q_FUNC_INFO << terminal->command(); 143 | if(terminal->command().mid(1).startsWith(QStringLiteral("stop"))) 144 | quit(); 145 | else if(terminal->terminalMode() == Service::TerminalMode::ReadWriteActive) { 146 | connect(terminal, &Terminal::readyRead, 147 | terminal, [terminal](){ 148 | qDebug() << Q_FUNC_INFO << terminal->readAll(); 149 | terminal->disconnectTerminal(); 150 | }); 151 | terminal->write("name: "); 152 | terminal->requestLine(); 153 | } else { 154 | connect(terminal, &Terminal::readyRead, 155 | terminal, [terminal](){ 156 | auto data = terminal->readAll(); 157 | qDebug() << Q_FUNC_INFO << data; 158 | terminal->write(data); 159 | }); 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /tests/auto/service/TestService/testservice.h: -------------------------------------------------------------------------------- 1 | #ifndef TESTSERVICE_H 2 | #define TESTSERVICE_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | class TestService : public QtService::Service 11 | { 12 | Q_OBJECT 13 | 14 | public: 15 | explicit TestService(int &argc, char **argv); 16 | 17 | protected: 18 | bool preStart() override; 19 | CommandResult onStart() override; 20 | CommandResult onStop(int &exitCode) override; 21 | CommandResult onReload() override; 22 | CommandResult onPause() override; 23 | CommandResult onResume() override; 24 | 25 | QVariant onCallback(const QByteArray &kind, const QVariantList &args) override; 26 | 27 | bool verifyCommand(const QStringList &arguments) override; 28 | 29 | protected Q_SLOTS: 30 | void terminalConnected(QtService::Terminal *terminal) override; 31 | 32 | private: 33 | QLocalServer *_server = nullptr; 34 | QLocalSocket *_socket = nullptr; 35 | QDataStream _stream; 36 | 37 | QTcpServer *_activatedServer = nullptr; 38 | }; 39 | 40 | #endif // TESTSERVICE_H 41 | -------------------------------------------------------------------------------- /tests/auto/service/TestStandardService/TestStandardService.pro: -------------------------------------------------------------------------------- 1 | include(../testlib.pri) 2 | 3 | TARGET = tst_standardservice 4 | 5 | SOURCES += \ 6 | tst_standardservice.cpp 7 | -------------------------------------------------------------------------------- /tests/auto/service/TestStandardService/tst_standardservice.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | class TestStandardService : public BasicServiceTest 7 | { 8 | Q_OBJECT 9 | 10 | protected: 11 | void init() override; 12 | QString backend() override; 13 | QString name() override; 14 | bool reportsStartErrors() override; 15 | }; 16 | 17 | void TestStandardService::init() 18 | { 19 | #ifdef Q_OS_WIN 20 | #ifdef QT_NO_DEBUG 21 | QString cPath = QCoreApplication::applicationDirPath() + QStringLiteral("/../../TestService/release"); 22 | #else 23 | QString cPath = QCoreApplication::applicationDirPath() + QStringLiteral("/../../TestService/debug"); 24 | #endif 25 | #else 26 | QString cPath = QCoreApplication::applicationDirPath() + QStringLiteral("/../TestService"); 27 | #endif 28 | cPath = QDir::cleanPath(cPath); 29 | cPath += QDir::listSeparator() + qEnvironmentVariable("PATH"); 30 | qputenv("PATH", cPath.toUtf8()); 31 | } 32 | 33 | QString TestStandardService::backend() 34 | { 35 | #ifdef QT_NO_DEBUG 36 | return QStringLiteral("standard"); 37 | #else 38 | return QStringLiteral("debug"); 39 | #endif 40 | } 41 | 42 | QString TestStandardService::name() 43 | { 44 | #ifdef Q_OS_WIN 45 | #ifdef QT_NO_DEBUG 46 | QString svcPath = QCoreApplication::applicationDirPath() + QStringLiteral("/../../TestService/release/testservice.exe"); 47 | #else 48 | QString svcPath = QCoreApplication::applicationDirPath() + QStringLiteral("/../../TestService/debug/testservice.exe"); 49 | #endif 50 | #else 51 | QString svcPath = QCoreApplication::applicationDirPath() + QStringLiteral("/../TestService/testservice"); 52 | #endif 53 | return QDir::cleanPath(svcPath); 54 | } 55 | 56 | bool TestStandardService::reportsStartErrors() 57 | { 58 | return false; 59 | } 60 | 61 | QTEST_MAIN(TestStandardService) 62 | 63 | #include "tst_standardservice.moc" 64 | -------------------------------------------------------------------------------- /tests/auto/service/TestSystemdService/TestSystemdService.pro: -------------------------------------------------------------------------------- 1 | include(../testlib.pri) 2 | 3 | TARGET = tst_systemdservice 4 | 5 | SOURCES += \ 6 | tst_systemdservice.cpp 7 | 8 | DISTFILES += \ 9 | testservice.service \ 10 | testservice.socket 11 | -------------------------------------------------------------------------------- /tests/auto/service/TestSystemdService/testservice.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=QtService Test Service 3 | Documentation=https://github.com/Skycoder42/QtService 4 | After=network-online.target testservice.socket 5 | 6 | [Service] 7 | Type=notify 8 | NotifyAccess=exec 9 | Environment=LD_LIBRARY_PATH=%{LD_LIBRARY_PATH} 10 | Environment=QT_PLUGIN_PATH=%{QT_PLUGIN_PATH} 11 | ExecStart=%{TESTSERVICE_PATH} --backend systemd 12 | ExecReload=%{TESTSERVICE_PATH} --backend systemd reload 13 | ExecStop=%{TESTSERVICE_PATH} --backend systemd stop 14 | Restart=on-abnormal 15 | RuntimeDirectory=testservice 16 | 17 | [Install] 18 | WantedBy=default.target 19 | -------------------------------------------------------------------------------- /tests/auto/service/TestSystemdService/testservice.socket: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=QtService Test Service Socket 3 | Documentation=https://github.com/Skycoder42/QtService 4 | After=network.target 5 | PartOf=testservice.service 6 | 7 | [Socket] 8 | ListenStream=15843 9 | #ListenStream=/run/user/1000/testservice/terminal.socket 10 | 11 | [Install] 12 | WantedBy=sockets.target 13 | -------------------------------------------------------------------------------- /tests/auto/service/TestSystemdService/tst_systemdservice.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | using namespace QtService; 10 | 11 | #define systemdpath QStringLiteral("systemd/user") 12 | #define testservice QStringLiteral("testservice.service") 13 | #define testsocket QStringLiteral("testservice.socket") 14 | 15 | class TestSystemdService : public BasicServiceTest 16 | { 17 | Q_OBJECT 18 | 19 | protected: 20 | QString backend() override; 21 | QString name() override; 22 | void init() override; 23 | void cleanup() override; 24 | void testCustomImpl() override; 25 | bool resetFailed() override; 26 | 27 | private Q_SLOTS: 28 | void testSocketActivation(); 29 | void testReloadFail(); 30 | 31 | private: 32 | bool daemonReload(); 33 | }; 34 | 35 | QString TestSystemdService::backend() 36 | { 37 | return QStringLiteral("systemd"); 38 | } 39 | 40 | QString TestSystemdService::name() 41 | { 42 | return testservice; 43 | } 44 | 45 | void TestSystemdService::init() 46 | { 47 | QDir srcDir(QStringLiteral(SRCDIR)); 48 | QVERIFY(srcDir.exists()); 49 | QVERIFY(srcDir.exists(testservice)); 50 | QVERIFY(srcDir.exists(testsocket)); 51 | 52 | auto systemdHome = QDir{QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation)}; 53 | QVERIFY(systemdHome.mkpath(systemdpath)); 54 | QVERIFY(systemdHome.cd(systemdpath)); 55 | 56 | if (!systemdHome.exists(testservice)) { 57 | QFile in{srcDir.absoluteFilePath(testservice)}; 58 | QVERIFY(in.open(QIODevice::ReadOnly | QIODevice::Text)); 59 | QFile out{systemdHome.absoluteFilePath(testservice)}; 60 | QVERIFY(out.open(QIODevice::WriteOnly | QIODevice::Text)); 61 | out.write(in.readAll() 62 | .replace("%{LD_LIBRARY_PATH}", qgetenv("LD_LIBRARY_PATH")) 63 | .replace("%{QT_PLUGIN_PATH}", qgetenv("QT_PLUGIN_PATH")) 64 | .replace("%{TESTSERVICE_PATH}", QString(QCoreApplication::applicationDirPath() + QStringLiteral("/../TestService/testservice")).toUtf8())); 65 | } 66 | if (!systemdHome.exists(testsocket)) 67 | QVERIFY(QFile::copy(srcDir.absoluteFilePath(testsocket), systemdHome.absoluteFilePath(testsocket))); 68 | 69 | QVERIFY(daemonReload()); 70 | resetFailed(); 71 | 72 | resetFailed(); 73 | } 74 | 75 | void TestSystemdService::cleanup() 76 | { 77 | resetFailed(); 78 | control->disableAutostart(); 79 | auto systemdHome = QDir{QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation)}; 80 | QVERIFY(systemdHome.cd(systemdpath)); 81 | QVERIFY(systemdHome.remove(testservice)); 82 | QVERIFY(systemdHome.remove(testsocket)); 83 | QVERIFY(daemonReload()); 84 | } 85 | 86 | void TestSystemdService::testCustomImpl() 87 | { 88 | QCOMPARE(control->status(), ServiceControl::Status::Running); 89 | 90 | // test pause 91 | QCOMPARE(control->callCommand("kill", QStringLiteral("--signal=SIGTSTP")), EXIT_SUCCESS); 92 | QByteArray msg; 93 | READ_LOOP(msg); 94 | QCOMPARE(msg, QByteArray("pausing")); 95 | 96 | // test resume 97 | QCOMPARE(control->callCommand("kill", QStringLiteral("--signal=SIGCONT")), EXIT_SUCCESS); 98 | READ_LOOP(msg); 99 | QCOMPARE(msg, QByteArray("resuming")); 100 | } 101 | 102 | void TestSystemdService::testSocketActivation() 103 | { 104 | resetFailed(); 105 | 106 | auto socketControl = ServiceControl::create(backend(), QStringLiteral("testservice.socket"), this); 107 | QVERIFY(socketControl->start()); 108 | 109 | performSocketTest(); 110 | 111 | QVERIFY(socketControl->stop()); 112 | } 113 | 114 | void TestSystemdService::testReloadFail() 115 | { 116 | QVERIFY(control->setBlocking(true)); 117 | QVERIFY2(control->start(), qUtf8Printable(control->error())); 118 | 119 | TEST_STATUS(ServiceControl::Status::Running); 120 | resetSettings({{QStringLiteral("fail"), true}}); 121 | 122 | QVERIFY(!control->reload()); 123 | 124 | if (control->supportFlags().testFlag(ServiceControl::SupportFlag::Status)) { 125 | auto ok = false; 126 | for (auto i = 0; i < 20; ++i) { 127 | if (const auto status = control->status(); 128 | status == ServiceControl::Status::Running || 129 | status == ServiceControl::Status::Errored) { 130 | ok = true; 131 | break; 132 | } else 133 | QThread::msleep(500); 134 | } 135 | if (!ok) 136 | QCOMPARE(control->status(), ServiceControl::Status::Unknown); 137 | } 138 | 139 | resetSettings(); 140 | if (control->status() == ServiceControl::Status::Running) 141 | QVERIFY(control->stop()); 142 | 143 | resetFailed(); 144 | TEST_STATUS(ServiceControl::Status::Stopped); 145 | } 146 | 147 | bool TestSystemdService::daemonReload() 148 | { 149 | QStringList args {QStringLiteral("daemon-reload")}; 150 | if (::geteuid() != 0) 151 | args.prepend(QStringLiteral("--user")); 152 | return QProcess::execute(QStringLiteral("systemctl"), args) == EXIT_SUCCESS; 153 | } 154 | 155 | bool TestSystemdService::resetFailed() 156 | { 157 | if (!control) 158 | return false; 159 | return control->callCommand("reset-failed") == EXIT_SUCCESS; 160 | } 161 | 162 | QTEST_MAIN(TestSystemdService) 163 | 164 | #include "tst_systemdservice.moc" 165 | -------------------------------------------------------------------------------- /tests/auto/service/TestTerminalService/TestTerminalService.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = app 2 | 3 | QT = core service testlib 4 | 5 | CONFIG += console 6 | CONFIG -= app_bundle 7 | 8 | TARGET = tst_terminalservice 9 | 10 | DEFINES += SRCDIR=\\\"$$_PRO_FILE_PWD_/\\\" 11 | 12 | SOURCES += \ 13 | tst_terminalservice.cpp 14 | 15 | include(../../testrun.pri) 16 | -------------------------------------------------------------------------------- /tests/auto/service/TestTerminalService/tst_terminalservice.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | class TestTerminalService : public QObject 7 | { 8 | Q_OBJECT 9 | 10 | private Q_SLOTS: 11 | void initTestCase(); 12 | void cleanupTestCase(); 13 | 14 | void testPassiveTerminal(); 15 | void testActiveTerminal(); 16 | void testTermStop(); 17 | 18 | private: 19 | QString svcPath; 20 | 21 | QProcess *createProc(QStringList args = {}); 22 | }; 23 | 24 | void TestTerminalService::initTestCase() 25 | { 26 | #ifdef Q_OS_WIN 27 | #ifdef QT_NO_DEBUG 28 | svcPath = QCoreApplication::applicationDirPath() + QStringLiteral("/../../TestService/release/testservice.exe"); 29 | #else 30 | svcPath = QCoreApplication::applicationDirPath() + QStringLiteral("/../../TestService/debug/testservice.exe"); 31 | #endif 32 | #else 33 | svcPath = QCoreApplication::applicationDirPath() + QStringLiteral("/../TestService/testservice"); 34 | #endif 35 | QVERIFY2(QFile::exists(svcPath), qUtf8Printable(svcPath)); 36 | } 37 | 38 | void TestTerminalService::cleanupTestCase() 39 | { 40 | } 41 | 42 | void TestTerminalService::testPassiveTerminal() 43 | { 44 | auto proc = createProc({QStringLiteral("--passive")}); 45 | QVERIFY2(proc->waitForStarted(5000), qUtf8Printable(proc->errorString())); 46 | QThread::sleep(2); 47 | 48 | proc->write("terminal test\n"); 49 | QVERIFY(proc->waitForBytesWritten(5000)); 50 | QVERIFY(proc->waitForReadyRead(5000)); 51 | QCOMPARE(proc->readAll(), QByteArray{"terminal test\n"}); 52 | 53 | proc->write("another test\n"); 54 | QVERIFY(proc->waitForBytesWritten(5000)); 55 | QVERIFY(proc->waitForReadyRead(5000)); 56 | QCOMPARE(proc->readAll(), QByteArray{"another test\n"}); 57 | 58 | proc->terminate(); 59 | if(!proc->waitForFinished(5000)) 60 | proc->kill(); 61 | 62 | proc->deleteLater(); 63 | } 64 | 65 | void TestTerminalService::testActiveTerminal() 66 | { 67 | auto proc = createProc(); 68 | QVERIFY2(proc->waitForStarted(5000), qUtf8Printable(proc->errorString())); 69 | 70 | QVERIFY(proc->waitForReadyRead(5000)); 71 | QCOMPARE(proc->readAll(), QByteArray{"name: "}); 72 | 73 | proc->write("terminal test\n"); 74 | QVERIFY(proc->waitForBytesWritten(5000)); 75 | QVERIFY(proc->waitForFinished(5000)); 76 | 77 | proc->deleteLater(); 78 | } 79 | 80 | void TestTerminalService::testTermStop() 81 | { 82 | auto proc = createProc({QStringLiteral("stop")}); 83 | QVERIFY2(proc->waitForStarted(5000), qUtf8Printable(proc->errorString())); 84 | QVERIFY(proc->waitForFinished(5000)); 85 | 86 | proc->deleteLater(); 87 | } 88 | 89 | QProcess *TestTerminalService::createProc(QStringList args) 90 | { 91 | args.prepend(QStringLiteral("--terminal")); 92 | args.prepend(QStringLiteral("standard")); 93 | args.prepend(QStringLiteral("--backend")); 94 | 95 | auto proc = new QProcess{this}; 96 | proc->setProgram(svcPath); 97 | proc->setArguments(args); 98 | proc->setProcessChannelMode(QProcess::ForwardedErrorChannel); 99 | proc->start(QIODevice::ReadWrite | QIODevice::Text); 100 | 101 | return proc; 102 | } 103 | 104 | QTEST_MAIN(TestTerminalService) 105 | 106 | #include "tst_terminalservice.moc" 107 | -------------------------------------------------------------------------------- /tests/auto/service/TestWindowsService/TestWindowsService.pro: -------------------------------------------------------------------------------- 1 | include(../testlib.pri) 2 | 3 | TARGET = tst_windowsservice 4 | 5 | SOURCES += \ 6 | tst_windowsservice.cpp 7 | 8 | LIBS += -ladvapi32 9 | 10 | DEFINES+=QT_LIB_DIR=\\\"$$[QT_INSTALL_BINS]\\\" 11 | -------------------------------------------------------------------------------- /tests/auto/service/TestWindowsService/tst_windowsservice.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | using namespace QtService; 7 | 8 | #ifdef QT_NO_DEBUG 9 | #define LIB(x) QStringLiteral(x ".dll") 10 | #else 11 | #define LIB(x) QStringLiteral(x "d.dll") 12 | #endif 13 | 14 | class TestWindowsService : public BasicServiceTest 15 | { 16 | Q_OBJECT 17 | 18 | protected: 19 | QString backend() override; 20 | QString name() override; 21 | void init() override; 22 | void cleanup() override; 23 | void testCustomImpl() override; 24 | 25 | private: 26 | QTemporaryDir _svcDir; 27 | SC_HANDLE _manager = nullptr; 28 | bool _printLog = false; 29 | }; 30 | 31 | QString TestWindowsService::backend() 32 | { 33 | return QStringLiteral("windows"); 34 | } 35 | 36 | QString TestWindowsService::name() 37 | { 38 | return QStringLiteral("testservice"); 39 | } 40 | 41 | void TestWindowsService::init() 42 | { 43 | QDir svcDir{_svcDir.path()}; 44 | QVERIFY(svcDir.mkpath(QStringLiteral("."))); 45 | QVERIFY(svcDir.exists()); 46 | 47 | // copy the primary executable 48 | const auto svcName = QStringLiteral("testservice.exe"); 49 | #ifdef QT_NO_DEBUG 50 | const QString svcSrcPath{QCoreApplication::applicationDirPath() + QStringLiteral("/../../TestService/release/") + svcName}; 51 | const auto deployType = QStringLiteral("--release"); 52 | #else 53 | const QString svcSrcPath{QCoreApplication::applicationDirPath() + QStringLiteral("/../../TestService/debug/") + svcName}; 54 | const auto deployType = QStringLiteral("--debug"); 55 | #endif 56 | QVERIFY(QFile::exists(svcSrcPath)); 57 | QVERIFY(QFile::copy(svcSrcPath, svcDir.absoluteFilePath(svcName))); 58 | const auto svcArg = QStringLiteral("\"%1\" --backend windows").arg(QDir::toNativeSeparators(svcDir.absoluteFilePath(svcName))); 59 | 60 | // copy svc lib into host lib dir (required by windeployqt) 61 | const auto svcLib = LIB("Qt5Service"); 62 | const QDir bLibDir{QCoreApplication::applicationDirPath() + QStringLiteral("/../../../../../lib")}; 63 | QDir hLibDir{QStringLiteral(QT_LIB_DIR)}; 64 | QVERIFY(bLibDir.exists(svcLib)); 65 | hLibDir.remove(svcLib); 66 | QVERIFY(QFile::copy(bLibDir.absoluteFilePath(svcLib), hLibDir.absoluteFilePath(svcLib))); 67 | 68 | // run windeployqt 69 | QProcess windepProc; 70 | windepProc.setProgram(QStringLiteral("windeployqt.exe")); // should be in path 71 | windepProc.setArguments(QStringList{ 72 | deployType, 73 | QStringLiteral("--pdb"), 74 | QStringLiteral("--no-quick-import"), 75 | QStringLiteral("--no-translations"), 76 | QStringLiteral("--compiler-runtime"), 77 | svcName 78 | }); 79 | auto env = QProcessEnvironment::systemEnvironment(); 80 | env.insert(QStringLiteral("VCINSTALLDIR"), QStringLiteral("%1\\VC").arg(qEnvironmentVariable("VSINSTALLDIR"))); 81 | windepProc.setProcessEnvironment(env); 82 | windepProc.setWorkingDirectory(svcDir.absolutePath()); 83 | windepProc.setProcessChannelMode(QProcess::SeparateChannels); 84 | windepProc.setStandardOutputFile(QProcess::nullDevice()); 85 | windepProc.start(); 86 | QVERIFY2(windepProc.waitForFinished(), qUtf8Printable(windepProc.errorString())); 87 | qInfo() << "windeployqt errors:" << windepProc.readAllStandardError().constData(); 88 | QVERIFY2(windepProc.exitStatus() == QProcess::NormalExit, qUtf8Printable(windepProc.errorString())); 89 | QCOMPARE(windepProc.exitCode(), EXIT_SUCCESS); 90 | 91 | // write qt.conf 92 | QFile qtConf {svcDir.absoluteFilePath(QStringLiteral("qt.conf"))}; 93 | QVERIFY(qtConf.open(QIODevice::WriteOnly | QIODevice::Text)); 94 | qtConf.write("[Paths]\n"); 95 | qtConf.write("Prefix=.\n"); 96 | qtConf.write("Binaries=.\n"); 97 | qtConf.write("Libraries=.\n"); 98 | qtConf.write("Plugins=.\n"); 99 | qtConf.close(); 100 | 101 | // add plugins to Qt 102 | const auto plgSubDir = QStringLiteral("servicebackends"); 103 | QDir bPlgDir{QCoreApplication::applicationDirPath() + QStringLiteral("/../../../../../plugins/") + plgSubDir}; 104 | QVERIFY(bPlgDir.exists()); 105 | QVERIFY(svcDir.mkpath(plgSubDir)); 106 | QVERIFY(svcDir.cd(plgSubDir)); 107 | bPlgDir.setFilter(QDir::NoDotAndDotDot | QDir::Files); 108 | QDirIterator iter{bPlgDir, QDirIterator::NoIteratorFlags}; 109 | while (iter.hasNext()) { 110 | iter.next(); 111 | qDebug() << "Found service plugin file:" << iter.fileName(); 112 | QVERIFY(QFile::copy(iter.filePath(), svcDir.absoluteFilePath(iter.fileName()))); 113 | } 114 | QVERIFY(svcDir.cdUp()); 115 | 116 | _manager = OpenSCManagerW(nullptr, nullptr, 117 | SC_MANAGER_CONNECT | SC_MANAGER_CREATE_SERVICE | STANDARD_RIGHTS_REQUIRED); 118 | QVERIFY2(_manager, qUtf8Printable(qt_error_string(GetLastError()))); 119 | auto handle = CreateServiceW(_manager, 120 | reinterpret_cast(name().utf16()), 121 | L"Test Service", 122 | SERVICE_CHANGE_CONFIG, 123 | SERVICE_WIN32_OWN_PROCESS, 124 | SERVICE_DEMAND_START, 125 | SERVICE_ERROR_IGNORE, 126 | reinterpret_cast(svcArg.utf16()), 127 | nullptr, 128 | nullptr, 129 | nullptr, 130 | nullptr, 131 | nullptr); 132 | QVERIFY2(handle, qUtf8Printable(qt_error_string(GetLastError()))); 133 | 134 | CloseServiceHandle(handle); 135 | } 136 | 137 | void TestWindowsService::cleanup() 138 | { 139 | // Print eventlog in hopes for some error info: 140 | if (_printLog) 141 | QProcess::execute(QStringLiteral("wevtutil qe Application")); 142 | 143 | if(_manager) { 144 | auto handle = OpenServiceW(_manager, 145 | reinterpret_cast(name().utf16()), 146 | DELETE); 147 | QVERIFY2(handle, qUtf8Printable(qt_error_string(GetLastError()))); 148 | QVERIFY2(DeleteService(handle), qUtf8Printable(qt_error_string(GetLastError()))); 149 | 150 | CloseServiceHandle(handle); 151 | CloseServiceHandle(_manager); 152 | _manager = nullptr; 153 | } 154 | 155 | qDebug() << "cleanup result:" << _svcDir.remove(); 156 | } 157 | 158 | void TestWindowsService::testCustomImpl() 159 | { 160 | _printLog = true; 161 | testFeature(ServiceControl::SupportFlag::Status); 162 | QCOMPARE(control->status(), ServiceControl::Status::Running); 163 | 164 | testFeature(ServiceControl::SupportFlag::CustomCommands); 165 | QVERIFY2(control->callCommand("command", 142), qUtf8Printable(control->error())); 166 | 167 | QByteArray msg; 168 | QVariantList args; 169 | READ_LOOP(msg >> args); 170 | QCOMPARE(msg, QByteArray{"command"}); 171 | QCOMPARE(args, QVariantList{142}); 172 | 173 | testFeature(ServiceControl::SupportFlag::Status); 174 | QCOMPARE(control->status(), ServiceControl::Status::Running); 175 | _printLog = false; 176 | } 177 | 178 | QTEST_MAIN(TestWindowsService) 179 | 180 | #include "tst_windowsservice.moc" 181 | -------------------------------------------------------------------------------- /tests/auto/service/service.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = subdirs 2 | 3 | SUBDIRS += \ 4 | TestBaseLib \ 5 | TestService \ 6 | TestStandardService \ 7 | TestTerminalService 8 | 9 | unix:!android:!ios:packagesExist(libsystemd):system(systemctl --version): SUBDIRS += TestSystemdService 10 | win32: SUBDIRS += TestWindowsService 11 | macx: SUBDIRS += TestLaunchdService 12 | 13 | TestStandardService.depends += TestBaseLib TestService 14 | TestTerminalService.depends += TestService 15 | TestSystemdService.depends += TestBaseLib TestService 16 | TestWindowsService.depends += TestBaseLib TestService 17 | TestLaunchdService.depends += TestBaseLib TestService 18 | 19 | prepareRecursiveTarget(run-tests) 20 | QMAKE_EXTRA_TARGETS += run-tests 21 | -------------------------------------------------------------------------------- /tests/auto/service/testlib.pri: -------------------------------------------------------------------------------- 1 | TEMPLATE = app 2 | 3 | QT = core service testlib 4 | 5 | CONFIG += console 6 | CONFIG -= app_bundle 7 | 8 | DEFINES += SRCDIR=\\\"$$_PRO_FILE_PWD_/\\\" 9 | 10 | win32:!win32-g++:CONFIG(release, debug|release): LIBS += -L$$OUT_PWD/../TestBaseLib/release/ -ltestbase 11 | else:win32:!win32-g++:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../TestBaseLib/debug/ -ltestbase 12 | else: LIBS += -L$$OUT_PWD/../TestBaseLib/ -ltestbase 13 | 14 | INCLUDEPATH += $$PWD/TestBaseLib 15 | DEPENDPATH += $$PWD/TestBaseLib 16 | 17 | win32:!win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../TestBaseLib/release/testbase.lib 18 | else:win32:!win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../TestBaseLib/debug/testbase.lib 19 | else: PRE_TARGETDEPS += $$OUT_PWD/../TestBaseLib/libtestbase.a 20 | 21 | include(../testrun.pri) 22 | -------------------------------------------------------------------------------- /tests/auto/testrun.pri: -------------------------------------------------------------------------------- 1 | debug_and_release:!ReleaseBuild:!DebugBuild { 2 | runtarget.target = run-tests 3 | runtarget.CONFIG = recursive 4 | runtarget.recurse_target = run-tests 5 | QMAKE_EXTRA_TARGETS += runtarget 6 | } else { 7 | oneshell.target = .ONESHELL 8 | QMAKE_EXTRA_TARGETS += oneshell 9 | 10 | win32:!win32-g++ { 11 | CONFIG(debug, debug|release): outdir_helper = debug 12 | CONFIG(release, debug|release): outdir_helper = release 13 | runtarget.target = run-tests 14 | !compat_test: runtarget.depends += $(DESTDIR_TARGET) 15 | runtarget.commands += set PATH=$$shell_path($$shadowed($$dirname(_QMAKE_CONF_))/bin);$$shell_path($$[QT_INSTALL_BINS]);$(PATH) 16 | runtarget.commands += $$escape_expand(\\n\\t)set QT_PLUGIN_PATH=$$shadowed($$dirname(_QMAKE_CONF_))/plugins;$$[QT_INSTALL_PLUGINS];$(QT_PLUGIN_PATH) 17 | runtarget.commands += $$escape_expand(\\n\\t)set QML2_IMPORT_PATH=$$shadowed($$dirname(_QMAKE_CONF_))/qml;$$[QT_INSTALL_QML];$(QML2_IMPORT_PATH) 18 | !isEmpty(LOGGING_RULES): runtarget.commands += $$escape_expand(\\n\\t)set \"QT_LOGGING_RULES=$$LOGGING_RULES\" 19 | runtarget.commands += $$escape_expand(\\n\\t)if exist $${outdir_helper}\\fail del $${outdir_helper}\\fail 20 | runtarget.commands += $$escape_expand(\\n\\t)start /w call $(DESTDIR_TARGET) ^> $${outdir_helper}\\test.log ^|^| echo FAIL ^> $${outdir_helper}\\fail ^& exit 0 21 | runtarget.commands += $$escape_expand(\\n\\t)type $${outdir_helper}\\test.log 22 | runtarget.commands += $$escape_expand(\\n\\t)if exist $${outdir_helper}\\fail exit 42 23 | QMAKE_EXTRA_TARGETS += runtarget 24 | } else { 25 | win32-g++: QMAKE_DIRLIST_SEP = ";" 26 | runtarget.commands += export PATH=\"$$shell_path($$shadowed($$dirname(_QMAKE_CONF_))/bin):$$shell_path($$[QT_INSTALL_BINS]):$${LITERAL_DOLLAR}$${LITERAL_DOLLAR}PATH\" 27 | runtarget.commands += $$escape_expand(\\n\\t)export QT_PLUGIN_PATH=\"$$shadowed($$dirname(_QMAKE_CONF_))/plugins$${QMAKE_DIRLIST_SEP}$$[QT_INSTALL_PLUGINS]$${QMAKE_DIRLIST_SEP}$(QT_PLUGIN_PATH)\" 28 | runtarget.commands += $$escape_expand(\\n\\t)export QML2_IMPORT_PATH=\"$$shadowed($$dirname(_QMAKE_CONF_))/qml$${QMAKE_DIRLIST_SEP}$$[QT_INSTALL_QML]$${QMAKE_DIRLIST_SEP}$(QML2_IMPORT_PATH)\" 29 | !isEmpty(LOGGING_RULES): runtarget.commands += $$escape_expand(\\n\\t)export QT_LOGGING_RULES=\"$$LOGGING_RULES\" 30 | win32-g++: QMAKE_DIRLIST_SEP = ":" 31 | 32 | linux|win32-g++ { 33 | runtarget.commands += $$escape_expand(\\n\\t)export LD_LIBRARY_PATH=\"$$shadowed($$dirname(_QMAKE_CONF_))/lib$${QMAKE_DIRLIST_SEP}$$[QT_INSTALL_LIBS]$${QMAKE_DIRLIST_SEP}$(LD_LIBRARY_PATH)\" 34 | runtarget.commands += $$escape_expand(\\n\\t)export QT_QPA_PLATFORM=minimal 35 | } else:mac { 36 | runtarget.commands += $$escape_expand(\\n\\t)export DYLD_LIBRARY_PATH=\"$$shadowed($$dirname(_QMAKE_CONF_))/lib:$$[QT_INSTALL_LIBS]:$(DYLD_LIBRARY_PATH)\" 37 | runtarget.commands += $$escape_expand(\\n\\t)export DYLD_FRAMEWORK_PATH=\"$$shadowed($$dirname(_QMAKE_CONF_))/lib:$$[QT_INSTALL_LIBS]:$(DYLD_FRAMEWORK_PATH)\" 38 | } 39 | 40 | runtarget.target = run-tests 41 | win32-g++ { 42 | !compat_test: runtarget.depends += $(DESTDIR_TARGET) 43 | runtarget.commands += $$escape_expand(\\n\\t)./$(DESTDIR_TARGET) 44 | } else { 45 | !compat_test: runtarget.depends += $(TARGET) 46 | runtarget.commands += $$escape_expand(\\n\\t)./$(TARGET) 47 | } 48 | QMAKE_EXTRA_TARGETS += runtarget 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /tests/global/global.cfg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /tests/tests.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = subdirs 2 | 3 | CONFIG += no_docs_target 4 | 5 | SUBDIRS += auto 6 | 7 | prepareRecursiveTarget(run-tests) 8 | QMAKE_EXTRA_TARGETS += run-tests 9 | --------------------------------------------------------------------------------