22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |  |
30 |
31 |
32 |
33 | $projectname
34 | $projectnumber
35 |
36 | $projectbrief
37 | |
38 |
39 |
40 |
41 |
42 | $projectbrief
43 | |
44 |
45 |
46 |
47 |
48 | $searchbox |
49 |
50 |
51 |
52 |
53 |
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