diff --git a/connector/CMakeLists.txt b/connector/CMakeLists.txt
index 447c427..dc58883 100644
--- a/connector/CMakeLists.txt
+++ b/connector/CMakeLists.txt
@@ -45,6 +45,35 @@ list(APPEND CMAKE_MODULE_PATH ${QT_UTILITIES_MODULE_DIRS})
# link also explicitely against the following Qt 5 modules
list(APPEND ADDITIONAL_QT_MODULES Network)
+# configure support for D-Bus notifications
+option(SYSTEMD_SUPPORT "enables support for controlling Syncthing systemd service" ${UNIX})
+if(DBUS_NOTIFICATIONS)
+ list(APPEND HEADER_FILES
+ syncthingservice.h
+ )
+ list(APPEND SRC_FILES
+ syncthingservice.cpp
+ )
+ list(APPEND DBUS_FILES
+ org.freedesktop.DBus.Properties.xml
+ org.freedesktop.systemd1.Manager.xml
+ org.freedesktop.systemd1.Service.xml
+ org.freedesktop.systemd1.Unit.xml
+ )
+ set_source_files_properties(
+ org.freedesktop.systemd1.Manager.xml
+ PROPERTIES INCLUDE syncthingservice.h
+ )
+ list(APPEND META_PUBLIC_COMPILE_DEFINITIONS LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD)
+ message(STATUS "systemd support enabled")
+else()
+ list(APPEND DOC_ONLY_FILES
+ syncthingservice.h
+ syncthingservice.cpp
+ )
+ message(STATUS "systemd support disabled")
+endif()
+
# include modules to apply configuration
include(BasicConfig)
include(QtConfig)
diff --git a/connector/org.freedesktop.DBus.Properties.xml b/connector/org.freedesktop.DBus.Properties.xml
new file mode 100644
index 0000000..a0af918
--- /dev/null
+++ b/connector/org.freedesktop.DBus.Properties.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/connector/org.freedesktop.systemd1.Manager.xml b/connector/org.freedesktop.systemd1.Manager.xml
new file mode 100644
index 0000000..472c2ed
--- /dev/null
+++ b/connector/org.freedesktop.systemd1.Manager.xml
@@ -0,0 +1,573 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/connector/org.freedesktop.systemd1.Service.xml b/connector/org.freedesktop.systemd1.Service.xml
new file mode 100644
index 0000000..3e8897e
--- /dev/null
+++ b/connector/org.freedesktop.systemd1.Service.xml
@@ -0,0 +1,524 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -->
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/connector/org.freedesktop.systemd1.Unit.xml b/connector/org.freedesktop.systemd1.Unit.xml
new file mode 100644
index 0000000..fb6ad67
--- /dev/null
+++ b/connector/org.freedesktop.systemd1.Unit.xml
@@ -0,0 +1,233 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/connector/syncthingconnection.cpp b/connector/syncthingconnection.cpp
index e60b1b1..731c7bf 100644
--- a/connector/syncthingconnection.cpp
+++ b/connector/syncthingconnection.cpp
@@ -1,6 +1,7 @@
#include "./syncthingconnection.h"
#include "./syncthingconfig.h"
#include "./syncthingconnectionsettings.h"
+#include "./utils.h"
#include
#include
@@ -632,11 +633,7 @@ void SyncthingConnection::loadSelfSignedCertificate()
}
// only possible if the Syncthing instance is running on the local machine
- const QString host(syncthingUrl.host());
- const QHostAddress hostAddress(host);
- if(host.compare(QLatin1String("localhost"), Qt::CaseInsensitive) != 0
- && !hostAddress.isLoopback()
- && !QNetworkInterface::allAddresses().contains(hostAddress)) {
+ if(!isLocal(syncthingUrl)) {
return;
}
diff --git a/connector/syncthingservice.cpp b/connector/syncthingservice.cpp
new file mode 100644
index 0000000..23a9ef5
--- /dev/null
+++ b/connector/syncthingservice.cpp
@@ -0,0 +1,248 @@
+#include "./syncthingservice.h"
+
+#include "managerinterface.h"
+#include "unitinterface.h"
+#include "serviceinterface.h"
+#include "propertiesinterface.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+using namespace std;
+using namespace std::placeholders;
+
+namespace Data {
+
+QDBusArgument &operator<<(QDBusArgument &argument, const ManagerDBusUnitFileChange &unitFileChange)
+{
+ argument.beginStructure();
+ argument << unitFileChange.type << unitFileChange.path << unitFileChange.source;
+ argument.endStructure();
+ return argument;
+}
+
+const QDBusArgument &operator>>(const QDBusArgument &argument, ManagerDBusUnitFileChange &unitFileChange)
+{
+ argument.beginStructure();
+ argument >> unitFileChange.type >> unitFileChange.path >> unitFileChange.source;
+ argument.endStructure();
+ return argument;
+}
+
+OrgFreedesktopSystemd1ManagerInterface *SyncthingService::s_manager = nullptr;
+
+SyncthingService::SyncthingService(QObject *parent) :
+ QObject(parent),
+ m_unit(nullptr),
+ m_service(nullptr),
+ m_properties(nullptr)
+{
+ if(!s_manager) {
+ // register custom data types
+ qDBusRegisterMetaType();
+ qDBusRegisterMetaType();
+
+ s_manager = new OrgFreedesktopSystemd1ManagerInterface(
+ QStringLiteral("org.freedesktop.systemd1"),
+ QStringLiteral("/org/freedesktop/systemd1"),
+ QDBusConnection::sessionBus()
+ );
+
+ // enable systemd to emit signals
+ s_manager->Subscribe();
+ }
+ connect(s_manager, &OrgFreedesktopSystemd1ManagerInterface::UnitNew, this, &SyncthingService::handleUnitAdded);
+ connect(s_manager, &OrgFreedesktopSystemd1ManagerInterface::UnitRemoved, this, &SyncthingService::handleUnitRemoved);
+ m_serviceWatcher = new QDBusServiceWatcher(s_manager->service(), s_manager->connection());
+}
+
+void SyncthingService::setUnitName(const QString &unitName)
+{
+ if(m_unitName != unitName) {
+ m_unitName = unitName;
+
+ delete m_service, delete m_unit, delete m_properties;
+ m_service = nullptr, m_unit = nullptr, m_properties = nullptr;
+ setProperties(QString(), QString(), QString(), QString());
+
+ if(s_manager->isValid()) {
+ connect(new QDBusPendingCallWatcher(s_manager->GetUnit(m_unitName), this), &QDBusPendingCallWatcher::finished, this, &SyncthingService::handleUnitGet);
+ }
+ }
+}
+
+bool SyncthingService::isSystemdAvailable() const
+{
+ return s_manager && s_manager->isValid();
+}
+
+bool SyncthingService::isUnitAvailable() const
+{
+ return m_unit && m_unit->isValid();
+}
+
+void SyncthingService::setRunning(bool running)
+{
+ if(running) {
+ registerErrorHandler(s_manager->StartUnit(m_unitName, QStringLiteral("replace")), QT_TR_NOOP_UTF8("starting unit"));
+ } else {
+ registerErrorHandler(s_manager->StopUnit(m_unitName, QStringLiteral("replace")), QT_TR_NOOP_UTF8("stopping unit"));
+ }
+}
+
+void SyncthingService::setEnabled(bool enabled)
+{
+ if(enabled) {
+ registerErrorHandler(s_manager->EnableUnitFiles(QStringList(m_unitName), false, true), QT_TR_NOOP_UTF8("enabling unit"));
+ } else {
+ registerErrorHandler(s_manager->DisableUnitFiles(QStringList(m_unitName), false), QT_TR_NOOP_UTF8("disabling unit"));
+ }
+}
+
+void SyncthingService::handleUnitAdded(const QString &unitName, const QDBusObjectPath &unitPath)
+{
+ if(unitName == m_unitName) {
+ setUnit(unitPath);
+ }
+}
+
+void SyncthingService::handleUnitRemoved(const QString &unitName, const QDBusObjectPath &unitPath)
+{
+ Q_UNUSED(unitPath)
+ if(unitName == m_unitName) {
+ setUnit(QDBusObjectPath());
+ }
+}
+
+void SyncthingService::handleUnitGet(QDBusPendingCallWatcher *watcher)
+{
+ watcher->deleteLater();
+
+ const QDBusPendingReply unitReply = *watcher;
+ if(unitReply.isError()) {
+ return;
+ }
+
+ setUnit(unitReply.value());
+}
+
+void SyncthingService::handlePropertiesChanged(const QString &interface, const QVariantMap &changedProperties, const QStringList &invalidatedProperties)
+{
+ if(interface == m_unit->interface()) {
+ const bool running = isRunning();
+ if(handlePropertyChanged(m_activeState, &SyncthingService::activeStateChanged, QStringLiteral("ActiveState"), changedProperties, invalidatedProperties)
+ | handlePropertyChanged(m_subState, &SyncthingService::subStateChanged, QStringLiteral("SubState"), changedProperties, invalidatedProperties)) {
+ emit stateChanged(m_activeState, m_subState);
+ }
+ if(running != isRunning()) {
+ emit runningChanged(isRunning());
+ }
+
+ const bool enabled = isEnabled();
+ handlePropertyChanged(m_unitFileState, &SyncthingService::unitFileStateChanged, QStringLiteral("UnitFileState"), changedProperties, invalidatedProperties);
+ if(enabled != isEnabled()) {
+ emit enabledChanged(isEnabled());
+ }
+
+ handlePropertyChanged(m_description, &SyncthingService::descriptionChanged, QStringLiteral("Description"), changedProperties, invalidatedProperties);
+ }
+}
+
+void SyncthingService::handleError(const char *context, QDBusPendingCallWatcher *watcher)
+{
+ watcher->deleteLater();
+ const QDBusError error = watcher->error();
+ if(error.isValid()) {
+ emit errorOccurred(tr(context), error.name(), error.message());
+ }
+}
+
+bool SyncthingService::handlePropertyChanged(QString &variable, void (SyncthingService::*signal)(const QString &), const QString &propertyName, const QVariantMap &changedProperties, const QStringList &invalidatedProperties)
+{
+ const QVariant valueVariant(changedProperties[propertyName]);
+ if(valueVariant.isValid()) {
+ const QString valueString(valueVariant.toString());
+ if(valueString != variable) {
+ emit (this->*signal)(variable = valueString);
+ return true;
+ }
+ } else if(invalidatedProperties.contains(propertyName) && !variable.isEmpty()) {
+ variable.clear();
+ emit (this->*signal)(variable);
+ return true;
+ }
+ return false;
+}
+
+void SyncthingService::registerErrorHandler(const QDBusPendingCall &call, const char *context)
+{
+ connect(new QDBusPendingCallWatcher(call, this), &QDBusPendingCallWatcher::finished, bind(&SyncthingService::handleError, this, context, _1));
+}
+
+void SyncthingService::setUnit(const QDBusObjectPath &objectPath)
+{
+ // cleanup
+ delete m_service, delete m_unit, delete m_properties;
+ m_service = nullptr, m_unit = nullptr, m_properties = nullptr;
+
+ const QString path = objectPath.path();
+ if(path.isEmpty()) {
+ setProperties(QString(), QString(), QString(), QString());
+ return;
+ }
+
+ // init unit
+ m_unit = new OrgFreedesktopSystemd1UnitInterface(s_manager->service(), path, s_manager->connection());
+ setProperties(m_unit->activeState(), m_unit->subState(), m_unit->unitFileState(), m_unit->description());
+
+ // init properties
+ m_properties = new OrgFreedesktopDBusPropertiesInterface(s_manager->service(), path, s_manager->connection());
+ connect(m_properties, &OrgFreedesktopDBusPropertiesInterface::PropertiesChanged, this, &SyncthingService::handlePropertiesChanged);
+}
+
+void SyncthingService::setProperties(const QString &activeState, const QString &subState, const QString &unitFileState, const QString &description)
+{
+ const bool running = isRunning();
+ bool anyStateChanged = false;
+ if(m_activeState != activeState) {
+ emit activeStateChanged(m_activeState = activeState);
+ anyStateChanged = true;
+ }
+ if(m_subState != subState) {
+ emit subStateChanged(m_subState = subState);
+ anyStateChanged = true;
+ }
+ if(anyStateChanged) {
+ emit stateChanged(m_activeState, m_subState);
+ }
+ if(running != isRunning()) {
+ emit runningChanged(isRunning());
+ }
+
+ const bool enabled = isEnabled();
+ if(m_unitFileState != unitFileState) {
+ emit unitFileStateChanged(m_unitFileState = unitFileState);
+ }
+ if(enabled != isEnabled()) {
+ emit enabledChanged(isEnabled());
+ }
+
+ if(m_description != description) {
+ emit descriptionChanged(m_description = description);
+ }
+}
+
+SyncthingService &syncthingService()
+{
+ static SyncthingService service;
+ return service;
+}
+
+} // namespace Data
diff --git a/connector/syncthingservice.h b/connector/syncthingservice.h
new file mode 100644
index 0000000..bc42fed
--- /dev/null
+++ b/connector/syncthingservice.h
@@ -0,0 +1,170 @@
+#ifndef DATA_SYNCTHINGSERVICE_H
+#define DATA_SYNCTHINGSERVICE_H
+
+#include
+#include
+
+QT_FORWARD_DECLARE_CLASS(QDBusServiceWatcher)
+QT_FORWARD_DECLARE_CLASS(QDBusArgument)
+QT_FORWARD_DECLARE_CLASS(QDBusObjectPath)
+QT_FORWARD_DECLARE_CLASS(QDBusPendingCall)
+QT_FORWARD_DECLARE_CLASS(QDBusPendingCallWatcher)
+
+class OrgFreedesktopSystemd1ManagerInterface;
+class OrgFreedesktopSystemd1UnitInterface;
+class OrgFreedesktopSystemd1ServiceInterface;
+class OrgFreedesktopDBusPropertiesInterface;
+
+namespace Data {
+
+struct ManagerDBusUnitFileChange {
+ QString type;
+ QString path;
+ QString source;
+};
+
+QDBusArgument &operator<<(QDBusArgument &argument, const ManagerDBusUnitFileChange &unitFileChange);
+const QDBusArgument &operator>>(const QDBusArgument &argument, ManagerDBusUnitFileChange &unitFileChange);
+
+typedef QList ManagerDBusUnitFileChangeList;
+
+class SyncthingService : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(QString unitName READ unitName WRITE setUnitName)
+ Q_PROPERTY(bool systemdAvailable READ isSystemdAvailable NOTIFY systemdAvailableChanged)
+ Q_PROPERTY(bool unitAvailable READ isUnitAvailable)
+ Q_PROPERTY(QString activeState READ activeState NOTIFY activeStateChanged)
+ Q_PROPERTY(QString subState READ subState NOTIFY subStateChanged)
+ Q_PROPERTY(QString unitFileState READ unitFileState NOTIFY unitFileStateChanged)
+ Q_PROPERTY(QString description READ description NOTIFY descriptionChanged)
+ Q_PROPERTY(bool running READ isRunning WRITE setRunning NOTIFY runningChanged)
+ Q_PROPERTY(bool enable READ isEnabled WRITE setEnabled NOTIFY enabledChanged)
+
+public:
+ SyncthingService(QObject *parent = nullptr);
+
+ const QString &unitName() const;
+ bool isSystemdAvailable() const;
+ bool isUnitAvailable() const;
+ const QString &activeState() const;
+ const QString &subState() const;
+ const QString &unitFileState() const;
+ const QString &description() const;
+ bool isRunning() const;
+ bool isEnabled() const;
+
+public Q_SLOTS:
+ void setUnitName(const QString &unitName);
+ void setRunning(bool running);
+ void start();
+ void stop();
+ void toggleRunning();
+ void setEnabled(bool enable);
+ void enable();
+ void disable();
+
+Q_SIGNALS:
+ void systemdAvailableChanged(bool available);
+ void stateChanged(const QString &activeState, const QString &subState);
+ void activeStateChanged(const QString &activeState);
+ void subStateChanged(const QString &subState);
+ void unitFileStateChanged(const QString &unitFileState);
+ void descriptionChanged(const QString &description);
+ void runningChanged(bool running);
+ void enabledChanged(bool enable);
+ void errorOccurred(const QString &context, const QString &name, const QString &message);
+
+private Q_SLOTS:
+ void handleUnitAdded(const QString &unitName, const QDBusObjectPath &unitPath);
+ void handleUnitRemoved(const QString &unitName, const QDBusObjectPath &unitPath);
+ void handleUnitGet(QDBusPendingCallWatcher *watcher);
+ void handlePropertiesChanged(const QString &interface, const QVariantMap &changedProperties, const QStringList &invalidatedProperties);
+ void handleError(const char *error, QDBusPendingCallWatcher *watcher);
+ void setUnit(const QDBusObjectPath &objectPath);
+ void setProperties(const QString &activeState, const QString &subState, const QString &unitFileState, const QString &description);
+
+private:
+ bool handlePropertyChanged(QString &variable, void(SyncthingService::*signal)(const QString &), const QString &propertyName, const QVariantMap &changedProperties, const QStringList &invalidatedProperties);
+ void registerErrorHandler(const QDBusPendingCall &call, const char *context);
+
+ static OrgFreedesktopSystemd1ManagerInterface *s_manager;
+ QString m_unitName;
+ QDBusServiceWatcher *m_serviceWatcher;
+ OrgFreedesktopSystemd1UnitInterface *m_unit;
+ OrgFreedesktopSystemd1ServiceInterface *m_service;
+ OrgFreedesktopDBusPropertiesInterface *m_properties;
+ QString m_description;
+ QString m_activeState;
+ QString m_subState;
+ QString m_unitFileState;
+};
+
+inline const QString &SyncthingService::unitName() const
+{
+ return m_unitName;
+}
+
+inline const QString &SyncthingService::activeState() const
+{
+ return m_activeState;
+}
+
+inline const QString &SyncthingService::subState() const
+{
+ return m_subState;
+}
+
+inline const QString &SyncthingService::unitFileState() const
+{
+ return m_unitFileState;
+}
+
+inline const QString &SyncthingService::description() const
+{
+ return m_description;
+}
+
+inline bool SyncthingService::isRunning() const
+{
+ return m_activeState == QLatin1String("active") && m_subState == QLatin1String("running");
+}
+
+inline void SyncthingService::start()
+{
+ setRunning(true);
+}
+
+inline void SyncthingService::stop()
+{
+ setRunning(false);
+}
+
+inline void SyncthingService::toggleRunning()
+{
+ setRunning(!isRunning());
+}
+
+inline bool SyncthingService::isEnabled() const
+{
+ return m_unitFileState == QLatin1String("enabled");
+}
+
+inline void SyncthingService::enable()
+{
+ setEnabled(true);
+}
+
+inline void SyncthingService::disable()
+{
+ setEnabled(false);
+}
+
+SyncthingService &syncthingService();
+
+} // namespace Data
+
+Q_DECLARE_METATYPE(Data::ManagerDBusUnitFileChange)
+Q_DECLARE_METATYPE(Data::ManagerDBusUnitFileChangeList)
+
+#endif // DATA_SYNCTHINGSERVICE_H
diff --git a/connector/utils.cpp b/connector/utils.cpp
index f0e92f6..8f5ed7f 100644
--- a/connector/utils.cpp
+++ b/connector/utils.cpp
@@ -3,20 +3,38 @@
#include
#include
+#include
+#include
+#include
#include
using namespace ChronoUtilities;
namespace Data {
+/*!
+ * \brief Returns a string like "2 min 45 s ago" for the specified \a dateTime.
+ */
QString agoString(DateTime dateTime)
{
const TimeSpan delta(DateTime::now() - dateTime);
if(!delta.isNegative() && static_cast(delta.totalTicks()) > (TimeSpan::ticksPerMinute / 4uL)) {
- return QCoreApplication::translate("Data::Utils", "%1 ago").arg(QString::fromLatin1(delta.toString(TimeSpanOutputFormat::WithMeasures, true).data()));
+ return QCoreApplication::translate("Data::Utils", "%1 ago").arg(QString::fromUtf8(delta.toString(TimeSpanOutputFormat::WithMeasures, true).data()));
} else {
return QCoreApplication::translate("Data::Utils", "right now");
}
}
+/*!
+ * \brief Returns whether the host specified by the given \a url is the local machine.
+ */
+bool isLocal(const QUrl &url)
+{
+ const QString host(url.host());
+ const QHostAddress hostAddress(host);
+ return host.compare(QLatin1String("localhost"), Qt::CaseInsensitive) == 0
+ || hostAddress.isLoopback()
+ || QNetworkInterface::allAddresses().contains(hostAddress);
+}
+
}
diff --git a/connector/utils.h b/connector/utils.h
index 72044e4..54ab912 100644
--- a/connector/utils.h
+++ b/connector/utils.h
@@ -6,6 +6,7 @@
#include
QT_FORWARD_DECLARE_CLASS(QString)
+QT_FORWARD_DECLARE_CLASS(QUrl)
namespace ChronoUtilities {
class DateTime;
@@ -14,6 +15,7 @@ class DateTime;
namespace Data {
QString LIB_SYNCTHING_CONNECTOR_EXPORT agoString(ChronoUtilities::DateTime dateTime);
+bool LIB_SYNCTHING_CONNECTOR_EXPORT isLocal(const QUrl &url);
}
diff --git a/tray/CMakeLists.txt b/tray/CMakeLists.txt
index fb85e98..f15a2f3 100644
--- a/tray/CMakeLists.txt
+++ b/tray/CMakeLists.txt
@@ -52,6 +52,7 @@ set(WIDGETS_UI_FILES
gui/appearanceoptionpage.ui
gui/autostartoptionpage.ui
gui/launcheroptionpage.ui
+ gui/systemdoptionpage.ui
gui/webviewoptionpage.ui
)
diff --git a/tray/application/main.cpp b/tray/application/main.cpp
index 52a77d8..832b179 100644
--- a/tray/application/main.cpp
+++ b/tray/application/main.cpp
@@ -5,6 +5,9 @@
#include "../gui/traywidget.h"
#include "../../connector/syncthingprocess.h"
+#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
+# include "../../connector/syncthingservice.h"
+#endif
#include "resources/config.h"
@@ -20,6 +23,7 @@
#include
#include
#include
+#include
#include
@@ -28,9 +32,25 @@ using namespace ApplicationUtilities;
using namespace QtGui;
using namespace Data;
+#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
+void handleSystemdServiceError(const QString &context, const QString &name, const QString &message)
+{
+ QMessageBox msgBox;
+ msgBox.setIcon(QMessageBox::Critical);
+ msgBox.setText(QCoreApplication::translate("main", "Unable to ") + context);
+ msgBox.setInformativeText(name % QStringLiteral(": ") % message);
+ msgBox.exec();
+}
+#endif
+
int initSyncthingTray(bool windowed, bool waitForTray)
{
auto &v = Settings::values();
+#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
+ SyncthingService &service = syncthingService();
+ service.setUnitName(v.systemd.syncthingUnit);
+ QObject::connect(&service, &SyncthingService::errorOccurred, &handleSystemdServiceError);
+#endif
if(windowed) {
if(v.launcher.enabled) {
syncthingProcess().startSyncthing(v.launcher.syncthingCmd());
diff --git a/tray/application/settings.cpp b/tray/application/settings.cpp
index a6c2b12..b932833 100644
--- a/tray/application/settings.cpp
+++ b/tray/application/settings.cpp
@@ -96,6 +96,11 @@ void restore()
launcher.enabled = settings.value(QStringLiteral("launchSynchting"), launcher.enabled).toBool();
launcher.syncthingPath = settings.value(QStringLiteral("syncthingPath"), launcher.syncthingPath).toString();
launcher.syncthingArgs = settings.value(QStringLiteral("syncthingArgs"), launcher.syncthingArgs).toString();
+#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
+ auto &systemd = v.systemd;
+ systemd.syncthingUnit = settings.value(QStringLiteral("syncthingUnit"), systemd.syncthingUnit).toString();
+ systemd.showButton = settings.value(QStringLiteral("showButton"), systemd.showButton).toBool();
+#endif
settings.endGroup();
#if defined(SYNCTHINGTRAY_USE_WEBENGINE) || defined(SYNCTHINGTRAY_USE_WEBKIT)
@@ -158,6 +163,11 @@ void save()
settings.setValue(QStringLiteral("launchSynchting"), launcher.enabled);
settings.setValue(QStringLiteral("syncthingPath"), launcher.syncthingPath);
settings.setValue(QStringLiteral("syncthingArgs"), launcher.syncthingArgs);
+#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
+ const auto &systemd = v.systemd;
+ settings.setValue(QStringLiteral("syncthingUnit"), systemd.syncthingUnit);
+ settings.setValue(QStringLiteral("showButton"), systemd.showButton);
+#endif
settings.endGroup();
#if defined(SYNCTHINGTRAY_USE_WEBENGINE) || defined(SYNCTHINGTRAY_USE_WEBKIT)
diff --git a/tray/application/settings.h b/tray/application/settings.h
index 9ccda72..819a395 100644
--- a/tray/application/settings.h
+++ b/tray/application/settings.h
@@ -62,6 +62,14 @@ struct Launcher
QString syncthingCmd() const;
};
+#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
+struct Systemd
+{
+ QString syncthingUnit = QStringLiteral("syncthing.service");
+ bool showButton = true;
+};
+#endif
+
#if defined(SYNCTHINGTRAY_USE_WEBENGINE) || defined(SYNCTHINGTRAY_USE_WEBKIT)
struct WebView
{
@@ -82,6 +90,9 @@ struct Settings
#endif
Appearance appearance;
Launcher launcher;
+#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
+ Systemd systemd;
+#endif
#if defined(SYNCTHINGTRAY_USE_WEBENGINE) || defined(SYNCTHINGTRAY_USE_WEBKIT)
WebView webView;
#endif
diff --git a/tray/gui/settingsdialog.cpp b/tray/gui/settingsdialog.cpp
index d82f6d2..20b593b 100644
--- a/tray/gui/settingsdialog.cpp
+++ b/tray/gui/settingsdialog.cpp
@@ -3,12 +3,19 @@
#include "../../connector/syncthingconnection.h"
#include "../../connector/syncthingconfig.h"
#include "../../connector/syncthingprocess.h"
+#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
+# include "../../connector/syncthingservice.h"
+# include "../../model/colors.h"
+#endif
#include "ui_connectionoptionpage.h"
#include "ui_notificationsoptionpage.h"
#include "ui_appearanceoptionpage.h"
#include "ui_autostartoptionpage.h"
#include "ui_launcheroptionpage.h"
+#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
+# include "ui_systemdoptionpage.h"
+#endif
#include "ui_webviewoptionpage.h"
#include "resources/config.h"
@@ -19,6 +26,9 @@
#ifdef QT_UTILITIES_SUPPORT_DBUS_NOTIFICATIONS
# include
#endif
+#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
+# include
+#endif
#include
#include
@@ -556,6 +566,98 @@ void LauncherOptionPage::stop()
}
}
+// SystemdOptionPage
+#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
+SystemdOptionPage::SystemdOptionPage(QWidget *parentWidget) :
+ SystemdOptionPageBase(parentWidget),
+ m_service(syncthingService())
+{}
+
+SystemdOptionPage::~SystemdOptionPage()
+{}
+
+QWidget *SystemdOptionPage::setupWidget()
+{
+ auto *widget = SystemdOptionPageBase::setupWidget();
+ QObject::connect(ui()->syncthingUnitLineEdit, &QLineEdit::textChanged, &m_service, &SyncthingService::setUnitName);
+ QObject::connect(ui()->startPushButton, &QPushButton::clicked, &m_service, &SyncthingService::start);
+ QObject::connect(ui()->stopPushButton, &QPushButton::clicked, &m_service, &SyncthingService::stop);
+ QObject::connect(ui()->enablePushButton, &QPushButton::clicked, &m_service, &SyncthingService::enable);
+ QObject::connect(ui()->disablePushButton, &QPushButton::clicked, &m_service, &SyncthingService::disable);
+ QObject::connect(&m_service, &SyncthingService::descriptionChanged, bind(&SystemdOptionPage::handleDescriptionChanged, this, _1));
+ QObject::connect(&m_service, &SyncthingService::stateChanged, bind(&SystemdOptionPage::handleStatusChanged, this, _1, _2));
+ QObject::connect(&m_service, &SyncthingService::unitFileStateChanged, bind(&SystemdOptionPage::handleEnabledChanged, this, _1));
+ return widget;
+}
+
+bool SystemdOptionPage::apply()
+{
+ if(hasBeenShown()) {
+ auto &settings = values().systemd;
+ settings.syncthingUnit = ui()->syncthingUnitLineEdit->text();
+ settings.showButton = ui()->showButtonCheckBox->isChecked();
+ }
+ return true;
+}
+
+void SystemdOptionPage::reset()
+{
+ if(hasBeenShown()) {
+ const auto &settings = values().systemd;
+ ui()->syncthingUnitLineEdit->setText(settings.syncthingUnit);
+ ui()->showButtonCheckBox->setChecked(settings.showButton);
+ handleDescriptionChanged(m_service.description());
+ handleStatusChanged(m_service.activeState(), m_service.subState());
+ handleEnabledChanged(m_service.unitFileState());
+ }
+}
+
+void SystemdOptionPage::handleDescriptionChanged(const QString &description)
+{
+ ui()->descriptionValueLabel->setText(description.isEmpty() ? QCoreApplication::translate("QtGui::SystemdOptionPage", "specified unit is unknown") : description);
+}
+
+void setIndicatorColor(QWidget *indicator, const QColor &color)
+{
+ indicator->setStyleSheet(QStringLiteral("border-radius:8px;background-color:") + color.name());
+}
+
+void SystemdOptionPage::handleStatusChanged(const QString &activeState, const QString &subState)
+{
+ QStringList status;
+ if(!activeState.isEmpty()) {
+ status << activeState;
+ }
+ if(!subState.isEmpty()) {
+ status << subState;
+ }
+ const bool isRunning = m_service.isRunning();
+
+ ui()->statusValueLabel->setText(status.isEmpty()
+ ? QCoreApplication::translate("QtGui::SystemdOptionPage", "unknown")
+ : status.join(QStringLiteral(" - ")));
+ setIndicatorColor(ui()->statusIndicator, status.isEmpty()
+ ? Colors::gray(values().appearance.brightTextColors)
+ : (isRunning
+ ? Colors::green(values().appearance.brightTextColors)
+ : Colors::red(values().appearance.brightTextColors))
+ );
+ ui()->startPushButton->setVisible(!status.isEmpty() && !isRunning);
+ ui()->stopPushButton->setVisible(!status.isEmpty() && isRunning);
+}
+
+void SystemdOptionPage::handleEnabledChanged(const QString &unitFileState)
+{
+ const bool isEnabled = m_service.isEnabled();
+ ui()->unitFileStateValueLabel->setText(unitFileState.isEmpty() ? QCoreApplication::translate("QtGui::SystemdOptionPage", "unknown") : unitFileState);
+ setIndicatorColor(ui()->enabledIndicator, isEnabled
+ ? Colors::green(values().appearance.brightTextColors)
+ : Colors::gray(values().appearance.brightTextColors));
+ ui()->enablePushButton->setVisible(!unitFileState.isEmpty() && !isEnabled);
+ ui()->disablePushButton->setVisible(!unitFileState.isEmpty() && isEnabled);
+}
+#endif
+
// WebViewOptionPage
WebViewOptionPage::WebViewOptionPage(QWidget *parentWidget) :
WebViewOptionPageBase(parentWidget)
@@ -621,7 +723,11 @@ SettingsDialog::SettingsDialog(Data::SyncthingConnection *connection, QWidget *p
category = new OptionCategory(this);
category->setDisplayName(tr("Startup"));
- category->assignPages(QList() << new AutostartOptionPage << new LauncherOptionPage);
+ category->assignPages(QList() << new AutostartOptionPage << new LauncherOptionPage
+#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
+ << new SystemdOptionPage
+#endif
+ );
category->setIcon(QIcon::fromTheme(QStringLiteral("system-run"), QIcon(QStringLiteral(":/icons/hicolor/scalable/apps/system-run.svg"))));
categories << category;
@@ -647,6 +753,9 @@ INSTANTIATE_UI_FILE_BASED_OPTION_PAGE_NS(QtGui, NotificationsOptionPage)
INSTANTIATE_UI_FILE_BASED_OPTION_PAGE_NS(QtGui, AppearanceOptionPage)
INSTANTIATE_UI_FILE_BASED_OPTION_PAGE_NS(QtGui, AutostartOptionPage)
INSTANTIATE_UI_FILE_BASED_OPTION_PAGE_NS(QtGui, LauncherOptionPage)
+#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
+INSTANTIATE_UI_FILE_BASED_OPTION_PAGE_NS(QtGui, SystemdOptionPage)
+#endif
#ifndef SYNCTHINGTRAY_NO_WEBVIEW
INSTANTIATE_UI_FILE_BASED_OPTION_PAGE_NS(QtGui, WebViewOptionPage)
#endif
diff --git a/tray/gui/settingsdialog.h b/tray/gui/settingsdialog.h
index 916c0c1..f5b8118 100644
--- a/tray/gui/settingsdialog.h
+++ b/tray/gui/settingsdialog.h
@@ -12,6 +12,7 @@
namespace Data {
class SyncthingConnection;
+class SyncthingService;
}
namespace QtGui {
@@ -52,6 +53,17 @@ private:
bool m_kill;
END_DECLARE_OPTION_PAGE
+#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
+BEGIN_DECLARE_UI_FILE_BASED_OPTION_PAGE(SystemdOptionPage)
+private:
+ DECLARE_SETUP_WIDGETS
+ void handleDescriptionChanged(const QString &description);
+ void handleStatusChanged(const QString &activeState, const QString &subState);
+ void handleEnabledChanged(const QString &unitFileState);
+ Data::SyncthingService &m_service;
+END_DECLARE_OPTION_PAGE
+#endif
+
#ifndef SYNCTHINGTRAY_NO_WEBVIEW
DECLARE_UI_FILE_BASED_OPTION_PAGE(WebViewOptionPage)
#else
@@ -73,6 +85,9 @@ DECLARE_EXTERN_UI_FILE_BASED_OPTION_PAGE_NS(QtGui, NotificationsOptionPage)
DECLARE_EXTERN_UI_FILE_BASED_OPTION_PAGE_NS(QtGui, AppearanceOptionPage)
DECLARE_EXTERN_UI_FILE_BASED_OPTION_PAGE_NS(QtGui, AutostartOptionPage)
DECLARE_EXTERN_UI_FILE_BASED_OPTION_PAGE_NS(QtGui, LauncherOptionPage)
+#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
+DECLARE_EXTERN_UI_FILE_BASED_OPTION_PAGE_NS(QtGui, SystemdOptionPage)
+#endif
#ifndef SYNCTHINGTRAY_NO_WEBVIEW
DECLARE_EXTERN_UI_FILE_BASED_OPTION_PAGE_NS(QtGui, WebViewOptionPage)
#endif
diff --git a/tray/gui/systemdoptionpage.ui b/tray/gui/systemdoptionpage.ui
new file mode 100644
index 0000000..6ac091e
--- /dev/null
+++ b/tray/gui/systemdoptionpage.ui
@@ -0,0 +1,278 @@
+
+
+ QtGui::SystemdOptionPage
+
+
+ Systemd
+
+
+ -
+
+
+
+ 75
+ true
+
+
+
+ Show start/stop button on tray for local instance when specified unit available
+
+
+
+ -
+
+
+
+ 30
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ Syncthing unit
+
+
+
+ -
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Qt::Horizontal
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Description
+
+
+
+ -
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 0
+ 32
+
+
+
+ unknown
+
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Current status
+
+
+
+ -
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 16
+ 16
+
+
+
+ true
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 0
+ 32
+
+
+
+ unknown
+
+
+
+ -
+
+
+ Start
+
+
+
+ ..
+
+
+
+ -
+
+
+ Stop
+
+
+
+ ..
+
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Unit file state
+
+
+
+ -
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 16
+ 16
+
+
+
+ true
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 0
+ 32
+
+
+
+ unknown
+
+
+
+ -
+
+
+ Enable
+
+
+
+ ..
+
+
+
+ -
+
+
+ Disable
+
+
+
+ ..
+
+
+
+
+
+
+
+
+
+
+
+
+ Widgets::ClearLineEdit
+ QLineEdit
+ qtutilities/widgets/clearlineedit.h
+
+
+
+ showButtonCheckBox
+ syncthingUnitLineEdit
+ startPushButton
+ stopPushButton
+ enablePushButton
+ disablePushButton
+
+
+
+
diff --git a/tray/gui/traywidget.cpp b/tray/gui/traywidget.cpp
index 1e2ef3f..10050cb 100644
--- a/tray/gui/traywidget.cpp
+++ b/tray/gui/traywidget.cpp
@@ -7,6 +7,11 @@
#include "../application/settings.h"
+#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
+# include "../../connector/syncthingservice.h"
+# include "../../connector/utils.h"
+#endif
+
#include "resources/config.h"
#include "ui_traywidget.h"
@@ -129,6 +134,11 @@ TrayWidget::TrayWidget(TrayMenu *parent) :
connect(m_ui->notificationsPushButton, &QPushButton::clicked, this, &TrayWidget::showNotifications);
connect(restartButton, &QPushButton::clicked, this, &TrayWidget::restartSyncthing);
connect(m_connectionsActionGroup, &QActionGroup::triggered, this, &TrayWidget::handleConnectionSelected);
+#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
+ const SyncthingService &service = syncthingService();
+ connect(m_ui->startStopPushButton, &QPushButton::clicked, &service, &SyncthingService::toggleRunning);
+ connect(&service, &SyncthingService::stateChanged, this, &TrayWidget::updateStartStopButton);
+#endif
}
TrayWidget::~TrayWidget()
@@ -354,6 +364,11 @@ void TrayWidget::applySettings()
}
#endif
+ // systemd
+#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
+ instance->updateStartStopButton();
+#endif
+
// update visual appearance
instance->m_ui->trafficFormWidget->setVisible(settings.appearance.showTraffic);
instance->m_ui->trafficIconLabel->setVisible(settings.appearance.showTraffic);
@@ -458,6 +473,29 @@ void TrayWidget::updateTraffic()
}
+#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
+void TrayWidget::updateStartStopButton()
+{
+ const SyncthingService &service = syncthingService();
+ const Settings::Systemd &settings = Settings::values().systemd;
+
+ if(settings.showButton && service.isUnitAvailable() && m_selectedConnection && isLocal(QUrl(m_selectedConnection->syncthingUrl))) {
+ m_ui->startStopPushButton->setVisible(true);
+ if(service.isRunning()) {
+ m_ui->startStopPushButton->setText(tr("Stop"));
+ m_ui->startStopPushButton->setToolTip(QStringLiteral("systemctl --user stop ") + service.unitName());
+ m_ui->startStopPushButton->setIcon(QIcon::fromTheme(QStringLiteral("process-stop"), QIcon(QStringLiteral(":/icons/hicolor/scalable/actions/process-stop.svg"))));
+ } else {
+ m_ui->startStopPushButton->setText(tr("Start"));
+ m_ui->startStopPushButton->setToolTip(QStringLiteral("systemctl --user start ") + service.unitName());
+ m_ui->startStopPushButton->setIcon(QIcon::fromTheme(QStringLiteral("system-run"), QIcon(QStringLiteral(":/icons/hicolor/scalable/apps/system-run.svg"))));
+ }
+ } else {
+ m_ui->startStopPushButton->setVisible(false);
+ }
+}
+#endif
+
#ifndef SYNCTHINGTRAY_NO_WEBVIEW
void TrayWidget::handleWebViewDeleted()
{
@@ -480,6 +518,9 @@ void TrayWidget::handleConnectionSelected(QAction *connectionAction)
: &Settings::values().connection.secondary[static_cast(index - 1)];
m_ui->connectionsPushButton->setText(m_selectedConnection->label);
m_connection.reconnect(*m_selectedConnection);
+#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
+ updateStartStopButton();
+#endif
#ifndef SYNCTHINGTRAY_NO_WEBVIEW
if(m_webViewDlg) {
m_webViewDlg->applySettings(*m_selectedConnection);
diff --git a/tray/gui/traywidget.h b/tray/gui/traywidget.h
index 1f4e0a8..521776c 100644
--- a/tray/gui/traywidget.h
+++ b/tray/gui/traywidget.h
@@ -70,6 +70,9 @@ private slots:
void pauseResumeDev(const Data::SyncthingDev &dev);
void changeStatus();
void updateTraffic();
+#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
+ void updateStartStopButton();
+#endif
#ifndef SYNCTHINGTRAY_NO_WEBVIEW
void handleWebViewDeleted();
#endif
diff --git a/tray/gui/traywidget.ui b/tray/gui/traywidget.ui
index c57bc38..0409513 100644
--- a/tray/gui/traywidget.ui
+++ b/tray/gui/traywidget.ui
@@ -2,6 +2,14 @@
QtGui::TrayWidget
+
+
+ 0
+ 0
+ 300
+ 314
+
+
Syncthing Tray
@@ -107,6 +115,20 @@
+ -
+
+
+ Start
+
+
+
+ :/icons/hicolor/scalable/apps/system-run.svg:/icons/hicolor/scalable/apps/system-run.svg
+
+
+ true
+
+
+
-