Browse Source

changed general layout and improved handling of dbus calls

Malte Veerman 7 năm trước cách đây
mục cha
commit
37f61c969c

+ 44 - 67
fancontrol-gui/package/contents/ui/Application.qml

@@ -20,13 +20,13 @@
 
 import QtQuick 2.4
 import QtQuick.Controls 1.3
-import QtQuick.Dialogs 1.2
 import QtQuick.Layouts 1.1
 import Fancontrol.Qml 1.0 as Fancontrol
 
 
 ApplicationWindow {
     id: window
+
     title: i18n("Fancontrol-GUI")
     width: 1024
     height: 768
@@ -34,7 +34,6 @@ ApplicationWindow {
     visible: true
 
     onClosing: {
-        Fancontrol.base.save();
         windowConfig.save(window);
     }
 
@@ -43,49 +42,28 @@ ApplicationWindow {
         windowConfig.restore(window);
     }
 
-    menuBar: MenuBar {
-        Menu {
-            title: i18n("File")
-
-            MenuItem { action: loadAction }
-            MenuItem { action: saveAction }
-            MenuItem {
-                text: i18n("Save configuration file as")
-                onTriggered: saveFileDialog.open()
-                shortcut: StandardKey.SaveAs
-            }
-            MenuItem {
-                text: i18n("Exit")
-                onTriggered: Qt.quit()
-                shortcut: StandardKey.Quit
-            }
-        }
-    }
-
     toolBar: ToolBar {
         RowLayout {
             anchors.fill: parent
 
-            ToolButton { action: loadAction }
-            ToolButton { action: saveAction }
+            ToolButton {
+                action: applyAction
+            }
+            ToolButton {
+                action: resetAction
+            }
             Loader {
-                active: Fancontrol.base.hasSystemdCommunicator()
+                active: !!Fancontrol.base.systemdCom
+
                 sourceComponent: ToolButton {
-                    iconName: Fancontrol.base.systemdCom.serviceActive ? "system-reboot" : "system-run"
-                    onClicked: {
-                        Fancontrol.base.loader.abortTestingFans();
-                        Fancontrol.base.systemdCom.serviceActive ? Fancontrol.base.systemdCom.restartService() : Fancontrol.base.systemdCom.serviceActive = true;
-                    }
-                    tooltip: Fancontrol.base.systemdCom.serviceActive ? i18n("Restart fancontrol") : i18n("Start fancontrol")
+                    action: startAction
                 }
             }
             Loader {
-                active: Fancontrol.base.hasSystemdCommunicator()
+                active: !!Fancontrol.base.systemdCom
+
                 sourceComponent: ToolButton {
-                    iconName: "system-shutdown"
-                    enabled: Fancontrol.base.systemdCom.serviceActive
-                    onClicked: Fancontrol.base.systemdCom.serviceActive = false;
-                    tooltip: i18n("Stop fancontrol")
+                    action: stopAction
                 }
             }
             Item {
@@ -126,43 +104,42 @@ ApplicationWindow {
     }
 
     Action {
-        id: loadAction
-        text: i18n("Load configuration file")
-        iconName: "document-open"
-        onTriggered: openFileDialog.open()
-        tooltip: i18n("Load configuration file")
-        shortcut: StandardKey.Open
+        id: applyAction
+        text: i18n("Apply")
+        enabled: Fancontrol.base.needsApply
+        onTriggered: Fancontrol.base.apply()
+        iconName: "dialog-ok-apply"
+        tooltip: i18n("Apply changes")
+        shortcut: StandardKey.Apply
     }
     Action {
-        id: saveAction
-        text: i18n("Save configuration file")
-        onTriggered: Fancontrol.base.save(true)
-        iconName: "document-save"
-        tooltip: i18n("Save configuration file") + " (" + Fancontrol.base.loader.configPath + ")"
-        shortcut: StandardKey.Save
+        id: resetAction
+        text: i18n("Reset")
+        enabled: Fancontrol.base.needsApply
+        onTriggered: Fancontrol.base.reset()
+        iconName: "edit-undo"
+        tooltip: i18n("Revert changes")
     }
-
-    FileDialog {
-        id: openFileDialog
-        title: i18n("Please choose a configuration file")
-        folder: "file:///etc"
-        selectExisting: true
-        selectMultiple: false
-        modality: Qt.NonModal
-
-        onAccepted: Fancontrol.base.configUrl = fileUrl;
+    Action {
+        id: startAction
+        text: i18n("Start")
+        enabled: !!Fancontrol.base.systemdCom && !Fancontrol.base.systemdCom.serviceActive
+        onTriggered: {
+            Fancontrol.base.systemdCom.serviceActive = true;
+        }
+        iconName: "media-playback-start"
+        tooltip: i18n("Enable manual control")
     }
-    FileDialog {
-        id: saveFileDialog
-        title: i18n("Save configuration file as")
-        folder: "file:///etc"
-        selectExisting: false
-        selectMultiple: false
-        modality: Qt.NonModal
-
-        onAccepted: Fancontrol.base.save(true, fileUrl);
+    Action {
+        id: stopAction
+        text: i18n("Stop")
+        enabled: !!Fancontrol.base.systemdCom && Fancontrol.base.systemdCom.serviceActive
+        onTriggered: {
+            Fancontrol.base.systemdCom.serviceActive = false;
+        }
+        iconName: "media-playback-stop"
+        tooltip: i18n("Disable manual control")
     }
-
     SystemPalette {
         id: palette
     }

+ 63 - 2
fancontrol-gui/package/contents/ui/SettingsTab.qml

@@ -21,12 +21,13 @@
 import QtQuick 2.4
 import QtQuick.Layouts 1.1
 import QtQuick.Controls 1.2
+import QtQuick.Dialogs 1.2
 import Fancontrol.Qml 1.0 as Fancontrol
 
 
 Item {
     property QtObject systemdCom: Fancontrol.base.hasSystemdCommunicator() ? Fancontrol.base.systemdCom : null
-    property QtObject loader : Fancontrol.base.loader
+    property QtObject loader: Fancontrol.base.loader
     property int padding: 10
     property real textWidth: 0
     property var locale: Qt.locale()
@@ -52,12 +53,19 @@ Item {
                 Component.onCompleted: root.textWidth = Math.max(root.textWidth, contentWidth)
             }
             SpinBox {
+                id: intervalSpinBox
+
                 Layout.minimumWidth: implicitWidth
                 Layout.fillWidth: true
                 value: loader.interval
                 suffix: " " + i18np("second", "seconds", loader.interval)
                 minimumValue: 1.0
                 onValueChanged: loader.interval = value
+
+                Connections {
+                    target: loader
+                    onIntervalChanged: if (loader.interval != intervalSpinBox.value) intervalSpinBox.value = loader.interval
+                }
             }
         }
         RowLayout {
@@ -72,6 +80,7 @@ Item {
             }
             SpinBox {
                 id: minTempBox
+
                 Layout.minimumWidth: implicitWidth
                 Layout.fillWidth: true
                 decimals: 2
@@ -83,6 +92,11 @@ Item {
                     Fancontrol.base.minTemp = Fancontrol.Units.toCelsius(value, Fancontrol.base.unit);
                     if (value > maxTempBox.value) maxTempBox.value = value;
                 }
+
+                Connections {
+                    target: Fancontrol.base
+                    onMinTempChanged: if (Fancontrol.base.minTemp != minTempBox.value) minTempBox.value = Fancontrol.base.minTemp
+                }
             }
         }
         RowLayout {
@@ -97,6 +111,7 @@ Item {
             }
             SpinBox {
                 id: maxTempBox
+
                 Layout.minimumWidth: implicitWidth
                 Layout.fillWidth: true
                 decimals: 2
@@ -108,6 +123,34 @@ Item {
                     Fancontrol.base.maxTemp = Fancontrol.Units.toCelsius(value, Fancontrol.base.unit);
                     if (value < minTempBox.value) minTempBox.value = value;
                 }
+
+                Connections {
+                    target: Fancontrol.base
+                    onMaxTempChanged: if (Fancontrol.base.maxTemp != maxTempBox.value) maxTempBox.value = Fancontrol.base.maxTemp
+                }
+            }
+        }
+        RowLayout {
+            width: parent.width
+
+            Label {
+                Layout.preferredWidth: root.textWidth
+                clip: true
+                text: i18n("Path to the fancontrol config file:")
+                horizontalAlignment: Text.AlignRight
+                Component.onCompleted: root.textWidth = Math.max(root.textWidth, contentWidth)
+            }
+            Fancontrol.OptionInput {
+                id: fileInput
+
+                Layout.fillWidth: true
+                value: Fancontrol.base.configUrl.toString().replace("file://", "")
+                onTextChanged: Fancontrol.base.configUrl = text;
+            }
+            Button {
+                iconName: "document-open"
+                tooltip: i18n("Open config file")
+                onClicked: openFileDialog.open();
             }
         }
         Loader {
@@ -123,6 +166,8 @@ Item {
                     Component.onCompleted: root.textWidth = Math.max(root.textWidth, contentWidth)
                 }
                 Fancontrol.OptionInput {
+                    id: serviceNameInput
+
                     Layout.minimumWidth: implicitWidth
                     Layout.fillWidth: true
                     color: !!systemdCom && systemdCom.serviceExists ? "green" : "red"
@@ -139,12 +184,13 @@ Item {
                 Label {
                     Layout.preferredWidth: root.textWidth
                     clip: true
-                    text: i18n("Fancontrol systemd service autostart:")
+                    text: i18n("Enable service at boot:")
                     horizontalAlignment: Text.AlignRight
                     Component.onCompleted: root.textWidth = Math.max(root.textWidth, contentWidth)
                 }
                 CheckBox {
                     id: autostartBox
+
                     Layout.minimumWidth: implicitWidth
                     Layout.fillWidth: true
                     checked: !!systemdCom ? systemdCom.serviceEnabled : false
@@ -153,8 +199,23 @@ Item {
                             systemdCom.serviceEnabled = checked;
                         }
                     }
+
+                    Connections {
+                        target: systemdCom
+                        onServiceEnabledChanged: if (systemdCom.serviceEnabled != autostartBox.checked) autostartBox.checked = systemdCom.serviceEnabled
+                    }
                 }
             }
         }
+        FileDialog {
+            id: openFileDialog
+            title: i18n("Please choose a configuration file")
+            folder: "file:///etc"
+            selectExisting: true
+            selectMultiple: false
+            modality: Qt.NonModal
+
+            onAccepted: fileInput.text = fileUrl;
+        }
     }
 }

+ 130 - 41
helper/src/helper.cpp

@@ -29,8 +29,41 @@
 
 #ifndef NO_SYSTEMD
 #include <QtDBus/QDBusInterface>
+#include <QtDBus/QDBusMetaType>
+#include <QtDBus/QDBusReply>
+#include <QtDBus/QDBusVariant>
+
+
+struct StringStruct
+{
+    QString type;
+    QString filename;
+    QString destination;
+};
+
+typedef QList<StringStruct> StringStructArray;
+
+Q_DECLARE_METATYPE(StringStruct)
+Q_DECLARE_METATYPE(StringStructArray)
+
+QDBusArgument &operator<<(QDBusArgument &argument, const StringStruct &structure)
+{
+    argument.beginStructure();
+    argument << structure.type << structure.filename << structure.destination;
+    argument.endStructure();
+    return argument;
+}
+
+const QDBusArgument &operator>>(const QDBusArgument &argument, StringStruct &structure)
+{
+    argument.beginStructure();
+    argument >> structure.type >> structure.filename >> structure.destination;
+    argument.endStructure();
+    return argument;
+}
 #endif
 
+
 ActionReply Helper::action(const QVariantMap &arguments)
 {
     ActionReply reply;
@@ -38,54 +71,116 @@ ActionReply Helper::action(const QVariantMap &arguments)
 #ifndef NO_SYSTEMD
     if (arguments[QStringLiteral("action")] == "dbusaction")
     {
+        qDBusRegisterMetaType<StringStruct>();
+        qDBusRegisterMetaType<StringStructArray>();
+
         const auto method = arguments[QStringLiteral("method")].toString();
         const auto argsForCall = arguments[QStringLiteral("arguments")].toList();
 
         const auto systembus = QDBusConnection::systemBus();
-
         const auto iface = new QDBusInterface (QStringLiteral("org.freedesktop.systemd1"),
                                                QStringLiteral("/org/freedesktop/systemd1"),
                                                QStringLiteral("org.freedesktop.systemd1.Manager"),
                                                systembus,
                                                this);
-
-        QDBusMessage dbusreply;
+        QDBusMessage dbusmessage;
 
         if (iface->isValid())
-            dbusreply = iface->callWithArgumentList(QDBus::AutoDetect, method, argsForCall);
-        delete iface;
-
-        if (method != QStringLiteral("Reexecute"))
         {
-            if (dbusreply.type() == QDBusMessage::ErrorMessage)
+            if (argsForCall.isEmpty())
+                dbusmessage = iface->call(QDBus::AutoDetect, method);
+            else
+                dbusmessage = iface->callWithArgumentList(QDBus::AutoDetect, method, argsForCall);
+
+            if (method != QStringLiteral("Reexecute"))
             {
-                reply.setErrorCode(ActionReply::DBusError);
-                reply.setErrorDescription(dbusreply.errorMessage());
+                if (dbusmessage.type() == QDBusMessage::ErrorMessage)
+                {
+                    reply.setErrorCode(ActionReply::DBusError);
+                    reply.setErrorDescription(dbusmessage.errorMessage());
+                }
+                else if (dbusmessage.type() == QDBusMessage::ReplyMessage)
+                {
+                    if (dbusmessage.signature() == QStringLiteral("a(sss)"))
+                    {
+                        QDBusReply<StringStructArray> dbusreply(dbusmessage);
+                        if (dbusreply.isValid())
+                        {
+                            QMap<QString, QVariant> map;
+                            map.insert(QStringLiteral("type"), dbusreply.value().value(0).type);
+                            map.insert(QStringLiteral("filename"), dbusreply.value().value(0).filename);
+                            map.insert(QStringLiteral("destination"), dbusreply.value().value(0).destination);
+                            reply.addData(QStringLiteral("reply"), map);
+                        }
+                        else
+                        {
+                            reply = ActionReply::HelperErrorReply();
+                            reply.setErrorDescription(dbusreply.error().message());
+                        }
+                    }
+                    else if (dbusmessage.signature() == QStringLiteral("ba(sss)"))
+                    {
+                        QDBusReply<bool> dbusreply(dbusmessage); //QDBusReply only extracts the first return argument("b")
+                        if (dbusreply.isValid())
+                        {
+                            QMap<QString, QVariant> map;
+                            map.insert(QStringLiteral("enableInfo"), dbusreply.value());
+                            auto changes = qdbus_cast<StringStructArray>(qvariant_cast<QDBusArgument>(dbusmessage.arguments().value(1))); //Extract the second argument("a(sss)")
+                            map.insert(QStringLiteral("type"), changes.value(0).type);
+                            map.insert(QStringLiteral("filename"), changes.value(0).filename);
+                            map.insert(QStringLiteral("destination"), changes.value(0).destination);
+                            reply.addData(QStringLiteral("reply"), map);
+                        }
+                        else
+                        {
+                            reply = ActionReply::HelperErrorReply();
+                            reply.setErrorDescription(dbusreply.error().message());
+                        }
+                    }
+                    else if (dbusmessage.signature() == QStringLiteral("o"))
+                    {
+                        QDBusReply<QDBusObjectPath> dbusreply(dbusmessage);
+                        if (dbusreply.isValid())
+                        {
+                            QMap<QString, QVariant> map;
+                            map.insert(QStringLiteral("job"), dbusreply.value().path());
+                            reply.addData(QStringLiteral("reply"), map);
+                        }
+                        else
+                        {
+                            reply = ActionReply::HelperErrorReply();
+                            reply.setErrorDescription(dbusreply.error().message());
+                        }
+                    }
+                }
             }
         }
+        else
+        {
+            reply = ActionReply::HelperErrorReply();
+            reply.setErrorDescription(i18n("Could not create dbus interface"));
+        }
+        delete iface;
     }
     else
 #endif
-        if (arguments[QStringLiteral("action")] == "read")
+    if (arguments[QStringLiteral("action")] == "read")
     {
         const auto filename = arguments[QStringLiteral("filename")].toString();
         QFile file(filename);
 
-        if (!file.open(QIODevice::ReadOnly))
+        if (file.open(QIODevice::ReadOnly))
         {
-           reply = ActionReply::HelperErrorType;
-           reply.setErrorDescription(file.errorString());
+            QTextStream stream(&file);
+            const auto content = stream.readAll();
 
-           return reply;
+            reply.addData(QStringLiteral("content"), content);
+        }
+        else
+        {
+            reply = ActionReply::HelperErrorReply();
+            reply.setErrorDescription(file.errorString());
         }
-
-        QTextStream stream(&file);
-        const auto content = stream.readAll();
-
-        QVariantMap returnData;
-        returnData[QStringLiteral("content")] = content;
-
-        reply.setData(returnData);
     }
 
     else if (arguments[QStringLiteral("action")] == "write")
@@ -93,16 +188,16 @@ ActionReply Helper::action(const QVariantMap &arguments)
         const auto filename = arguments[QStringLiteral("filename")].toString();
         QFile file(filename);
 
-        if (!file.open(QIODevice::WriteOnly))
+        if (file.open(QIODevice::WriteOnly))
         {
-           reply = ActionReply::HelperErrorType;
-           reply.setErrorDescription(file.errorString());
-
-           return reply;
+            QTextStream stream(&file);
+            stream << arguments[QStringLiteral("content")].toString();
+        }
+        else
+        {
+            reply = ActionReply::HelperErrorReply();
+            reply.setErrorDescription(file.errorString());
         }
-
-        QTextStream stream(&file);
-        stream << arguments[QStringLiteral("content")].toString();
     }
 
     else if (arguments[QStringLiteral("action")] == "detectSensors")
@@ -115,25 +210,19 @@ ActionReply Helper::action(const QVariantMap &arguments)
 
         if (!process.waitForStarted(1000))
         {
-            reply = ActionReply::HelperErrorType;
+            reply = ActionReply::HelperErrorReply();
             reply.setErrorDescription(process.errorString());
-
-            return reply;
         }
-
-        if (!process.waitForFinished(10000))
+        else if (!process.waitForFinished(10000))
         {
-            reply = ActionReply::HelperErrorType;
+            reply = ActionReply::HelperErrorReply();
             reply.setErrorDescription(process.errorString());
-
-            return reply;
         }
     }
 
     else
     {
-        reply.setType(ActionReply::HelperErrorType);
-        reply.setErrorCode(ActionReply::NoSuchActionError);
+        reply = ActionReply::HelperErrorReply();
         reply.setErrorDescription(i18n("This action does not exist!"));
     }
 

+ 3 - 3
import/src/fan.cpp

@@ -52,7 +52,7 @@ Fan::Fan(uint index, Hwmon *parent) : Sensor(parent, index, parent ? parent->nam
         }
         else
         {
-            emit error(i18n("Can't open rpm file: \"%1\"", rpmFile->fileName()));
+            emit error(i18n("Can't open rpm file: \'%1\'", rpmFile->fileName()));
             delete rpmFile;
         }
     }
@@ -90,7 +90,7 @@ void Fan::setName(const QString &name)
     }
 }
 
-void Fan::reset()
+void Fan::toDefault()
 {
     if (m_rpmStream->device() && m_parent)
     {
@@ -109,7 +109,7 @@ void Fan::reset()
             }
             else
             {
-                emit error(i18n("Can't open rpm file: \"%1\"", rpmFile->fileName()));
+                emit error(i18n("Can't open rpm file: \'%1\'", rpmFile->fileName()));
                 delete rpmFile;
             }
         }

+ 1 - 1
import/src/fan.h

@@ -43,7 +43,7 @@ public:
     int rpm() const { return m_rpm; }
     QString name() const Q_DECL_OVERRIDE;
     void setName(const QString &name) Q_DECL_OVERRIDE;
-    void reset() Q_DECL_OVERRIDE;
+    void toDefault() Q_DECL_OVERRIDE;
     bool isValid() const Q_DECL_OVERRIDE;
 
     virtual int pwm() const { return 255; }

+ 71 - 15
import/src/guibase.cpp

@@ -29,6 +29,8 @@
 #include <QtCore/QLocale>
 #include <QtCore/QDebug>
 
+#include <KI18n/KLocalizedString>
+
 
 namespace Fancontrol
 {
@@ -42,14 +44,16 @@ GUIBase::GUIBase(QObject *parent) : QObject(parent),
 
     m_loader(new Loader(this)),
     m_configValid(false),
+    m_configChanged(false),
     m_pwmFanModel(new PwmFanModel(this)),
     m_tempModel(new TempModel(this))
 {
-    connect(m_config, &Config::configChanged, this, &GUIBase::emitConfigChanged);
     connect(this, &GUIBase::unitChanged, m_tempModel, &TempModel::setUnit);
+    connect(m_loader, &Loader::needsSaveChanged, this, &GUIBase::needsApplyChanged);
 
 #ifndef NO_SYSTEMD
     connect(m_loader, &Loader::requestSetServiceActive, m_com, &SystemdCommunicator::setServiceActive);
+    connect(m_com, &SystemdCommunicator::needsApplyChanged, this, &GUIBase::needsApplyChanged);
 #endif
 
     const auto locale = QLocale::system();
@@ -74,17 +78,13 @@ void GUIBase::load()
 
 #ifndef NO_SYSTEMD
     m_com->setServiceName(serviceName());
+    m_com->reset();
 #endif
 
-    emitConfigChanged();
-}
-
-void GUIBase::save(bool saveLoader, const QUrl &url)
-{
-    m_config->save();
-
-    if (saveLoader)
-        m_loader->save(url);
+    emit serviceNameChanged();
+    emit minTempChanged();
+    emit maxTempChanged();
+    emit configUrlChanged();
 }
 
 qreal GUIBase::maxTemp() const
@@ -113,6 +113,9 @@ void GUIBase::setMaxTemp(qreal temp)
     {
         m_config->findItem(QStringLiteral("MaxTemp"))->setProperty(temp);
         emit maxTempChanged();
+
+        m_configChanged = true;
+        emit needsApplyChanged();
     }
 }
 
@@ -122,6 +125,9 @@ void GUIBase::setMinTemp(qreal temp)
     {
         m_config->findItem(QStringLiteral("MinTemp"))->setProperty(temp);
         emit minTempChanged();
+
+        m_configChanged = true;
+        emit needsApplyChanged();
     }
 }
 
@@ -136,6 +142,9 @@ void GUIBase::setServiceName(const QString& name)
 #endif
 
         emit serviceNameChanged();
+
+        m_configChanged = true;
+        emit needsApplyChanged();
     }
 }
 
@@ -147,15 +156,19 @@ void GUIBase::setConfigUrl(const QUrl &url)
 
         m_config->findItem(QStringLiteral("ConfigUrl"))->setProperty(url.toString());
         emit configUrlChanged();
+
+        m_configChanged = true;
+        emit needsApplyChanged();
     }
 }
 
-void GUIBase::emitConfigChanged()
+bool GUIBase::needsApply() const
 {
-    emit serviceNameChanged();
-    emit minTempChanged();
-    emit maxTempChanged();
-    emit configUrlChanged();
+#ifndef NO_SYSTEMD
+    return m_loader->needsSave() || m_configChanged || m_com->needsApply();
+#else
+    return m_loader->needsSave() || m_configChanged;
+#endif
 }
 
 bool GUIBase::hasSystemdCommunicator() const
@@ -167,6 +180,40 @@ bool GUIBase::hasSystemdCommunicator() const
 #endif
 }
 
+void GUIBase::apply()
+{
+    qInfo() << i18n("Applying changes");
+
+    bool configChanged = m_loader->save();
+    m_config->save();
+
+#ifndef NO_SYSTEMD
+    m_com->apply(configChanged);
+#endif
+}
+
+void GUIBase::reset()
+{
+    qInfo() << i18n("Resetting changes");
+
+    m_config->load();
+    emit serviceNameChanged();
+    emit minTempChanged();
+    emit maxTempChanged();
+    emit configUrlChanged();
+    m_configChanged = false;
+
+    if (m_loader->needsSave() || configUrl() != m_loader->configUrl())
+        m_loader->load(configUrl());
+
+#ifndef NO_SYSTEMD
+    m_com->setServiceName(serviceName());
+    m_com->reset();
+#endif
+
+    emit needsApplyChanged();
+}
+
 void GUIBase::handleError(const QString &error, bool critical)
 {
     if (error.isEmpty() || error == m_error)
@@ -184,5 +231,14 @@ void GUIBase::handleError(const QString &error, bool critical)
         qWarning() << error;
 }
 
+void GUIBase::handleInfo(const QString &info)
+{
+    if (info.isEmpty())
+        return;
+
+    else
+        qInfo() << info;
+}
+
 
 }

+ 7 - 8
import/src/guibase.h

@@ -58,6 +58,7 @@ class GUIBase : public QObject
     Q_PROPERTY(QUrl configUrl READ configUrl WRITE setConfigUrl NOTIFY configUrlChanged)
     Q_PROPERTY(bool configValid READ configValid NOTIFY configUrlChanged)
     Q_PROPERTY(QString error READ error NOTIFY errorChanged)
+    Q_PROPERTY(bool needsApply READ needsApply NOTIFY needsApplyChanged)
 
 public:
 
@@ -81,18 +82,20 @@ public:
     void setServiceName(const QString &name);
     void setConfigUrl(const QUrl &url);
     void setUnit(const QString &unit) { if (unit != m_unit) { m_unit = unit; emit unitChanged(m_unit); } }
+    bool needsApply() const;
     PwmFanModel *pwmFanModel() const { return m_pwmFanModel; }
     TempModel *tempModel() const { return m_tempModel; }
 
     Q_INVOKABLE bool hasSystemdCommunicator() const;
+    Q_INVOKABLE void apply();
+    Q_INVOKABLE void reset();
 
 
 public slots:
 
-    void save(bool saveLoader = false, const QUrl &url = QUrl());
     void load();
     void handleError(const QString &error, bool critical = false);
-
+    void handleInfo(const QString &info);
 
 signals:
 
@@ -103,12 +106,7 @@ signals:
     void unitChanged(QString);
     void errorChanged();
     void criticalError();
-
-
-protected:
-
-    void emitConfigChanged();
-
+    void needsApplyChanged();
 
 private:
 
@@ -122,6 +120,7 @@ private:
     Loader *const m_loader;
     QString m_unit;
     bool m_configValid;
+    bool m_configChanged;
     PwmFanModel *m_pwmFanModel;
     TempModel *m_tempModel;
 };

+ 5 - 5
import/src/hwmon.cpp

@@ -91,7 +91,7 @@ void Hwmon::initialize()
 
         if (!success)
         {
-            emit error(i18n("Not a valid sensor: \"%1\"", entry));
+            emit error(i18n("Not a valid sensor: \'%1\'", entry));
             continue;
         }
 
@@ -138,7 +138,7 @@ void Hwmon::initialize()
                     if (fan->index() == index)
                     {
                         newFan = fan;
-                        newFan->reset();
+                        newFan->toDefault();
                         break;
                     }
                 }
@@ -162,7 +162,7 @@ void Hwmon::initialize()
                 if (temp->index() == index)
                 {
                     newTemp = temp;
-                    newTemp->reset();
+                    newTemp->toDefault();
                     break;
                 }
             }
@@ -251,10 +251,10 @@ bool Hwmon::testing() const
     return testing;
 }
 
-void Hwmon::reset() const
+void Hwmon::toDefault() const
 {
     for (const auto &pwmFan : m_pwmFans)
-        pwmFan->reset();
+        pwmFan->toDefault();
 }
 
 }

+ 1 - 1
import/src/hwmon.h

@@ -66,7 +66,7 @@ public:
     Temp * temp(int i) const;
     bool isValid() const { return m_valid; }
     bool testing() const;
-    void reset() const;
+    void toDefault() const;
 
 
 signals:

+ 52 - 42
import/src/loader.cpp

@@ -31,7 +31,6 @@
 #include <QtCore/QTextStream>
 #include <QtCore/QTimer>
 #include <QtCore/QProcess>
-#include <QtCore/QDebug>
 
 #include <KAuth/KAuthExecuteJob>
 #include <KI18n/KLocalizedString>
@@ -55,7 +54,10 @@ Loader::Loader(GUIBase *parent) : QObject(parent),
     m_sensorsDetected(false)
 {
     if (parent)
+    {
         connect(this, &Loader::error, parent, &GUIBase::handleError);
+        connect(this, &Loader::info, parent, &GUIBase::handleInfo);
+    }
 
     m_timer->setSingleShot(false);
     m_timer->start(1000);
@@ -72,12 +74,12 @@ void Loader::parseHwmons()
 
     else if (hwmonDir.exists())
     {
-        emit error(i18n("File is not readable: \"%1\"", QStringLiteral(HWMON_PATH)), true);
+        emit error(i18n("File is not readable: \'%1\'", QStringLiteral(HWMON_PATH)), true);
         return;
     }
     else
     {
-        emit error(i18n("File does not exist: \"%1\"", QStringLiteral(HWMON_PATH)), true);
+        emit error(i18n("File does not exist: \'%1\'", QStringLiteral(HWMON_PATH)), true);
         return;
     }
 
@@ -163,7 +165,7 @@ QPair<int, int> Loader::getEntryNumbers(const QString &entry)
     auto list = entry.split('/', QString::SkipEmptyParts);
     if (list.size() != 2)
     {
-        emit error(i18n("Invalid entry: \"%1\"", entry));
+        emit error(i18n("Invalid entry: \'%1\'", entry));
         return QPair<int, int>(-1, -1);
     }
     auto &hwmon = list[0];
@@ -171,12 +173,12 @@ QPair<int, int> Loader::getEntryNumbers(const QString &entry)
 
     if (!hwmon.startsWith(QStringLiteral("hwmon")))
     {
-        emit error(i18n("Invalid entry: \"%1\"", entry));
+        emit error(i18n("Invalid entry: \'%1\'", entry));
         return QPair<int, int>(-1, -1);
     }
     if (!sensor.contains(QRegExp("^(pwm|fan|temp)\\d+")))
     {
-        emit error(i18n("Invalid entry: \"%1\"", entry));
+        emit error(i18n("Invalid entry: \'%1\'", entry));
         return QPair<int, int>(-1, -1);
     }
 
@@ -189,13 +191,13 @@ QPair<int, int> Loader::getEntryNumbers(const QString &entry)
     const auto hwmonResult = hwmon.toInt(&success);
     if (!success)
     {
-        emit error(i18n("Invalid entry: \"%1\"", entry));
+        emit error(i18n("Invalid entry: \'%1\'", entry));
         return QPair<int, int>(-1, -1);
     }
     const auto sensorResult = sensor.toInt(&success);
     if (!success)
     {
-        emit error(i18n("Invalid entry: \"%1\"", entry));
+        emit error(i18n("Invalid entry: \'%1\'", entry));
         return QPair<int, int>(-1, -1);
     }
 
@@ -209,14 +211,9 @@ bool Loader::parseConfig(QString config)
     for (const auto &hwmon : m_hwmons)
     {
         disconnect(hwmon, &Hwmon::configUpdateNeeded, this, &Loader::updateConfig);
-        const auto pwmFans = hwmon->pwmFans();
-        for (const auto &pwmFan : pwmFans)
-        {
-            qobject_cast<PwmFan *>(pwmFan)->reset();
-        }
     }
 
-    reset();
+    toDefault();
 
     bool success = true;
     QTextStream stream;
@@ -252,7 +249,7 @@ bool Loader::parseConfig(QString config)
                 setInterval(interval, false);
             else
             {
-                emit error(i18n("Unable to parse interval line: \"%1\"", line), true);
+                emit error(i18n("Unable to parse interval line: \'%1\'", line), true);
                 success = false;
             }
         }
@@ -280,14 +277,14 @@ bool Loader::parseConfig(QString config)
                     else
                     {
                         if (!pwmPointer)
-                            emit error(i18n("Invalid fan entry: \"%1\"", pwmFanString), true);
+                            emit error(i18n("Invalid fan entry: \'%1\'", pwmFanString), true);
 
                         if (!tempPointer)
-                            emit error(i18n("Invalid temp entry: \"%1\"", tempString), true);
+                            emit error(i18n("Invalid temp entry: \'%1\'", tempString), true);
                     }
                 }
                 else
-                    emit error(i18n("Invalid entry: \"%1\"", fctemp), true);
+                    emit error(i18n("Invalid entry: \'%1\'", fctemp), true);
             }
         }
         else if (line.startsWith(QStringLiteral("DEVNAME=")))
@@ -309,13 +306,13 @@ bool Loader::parseConfig(QString config)
 
                     if (!intSuccess)
                     {
-                        emit error(i18n("Invalid DEVNAME: \"%1\"!", devname), true);
+                        emit error(i18n("Invalid DEVNAME: \'%1\'!", devname), true);
                         success = false;
                     }
 
                     if (!hwmonPointer)
                     {
-                        emit error(i18n("Invalid DEVNAME: \"%1\"! No hwmon with index %2", devname, index), true);
+                        emit error(i18n("Invalid DEVNAME: \'%1\'! No hwmon with index %2", devname, index), true);
                         success = false;
                     }
                     else if (hwmonPointer->name().split('.').first() != name)
@@ -325,7 +322,7 @@ bool Loader::parseConfig(QString config)
                     }
                 }
                 else
-                    emit error(i18n("Invalid DEVNAME: \"%1\"!", devname), true);
+                    emit error(i18n("Invalid DEVNAME: \'%1\'!", devname), true);
             }
         }
         else if (line.startsWith(QStringLiteral("MINTEMP=")))
@@ -361,7 +358,7 @@ bool Loader::parseConfig(QString config)
         else if (!line.startsWith(QStringLiteral("DEVPATH=")) &&
             !line.startsWith(QStringLiteral("FCFANS=")))
         {
-            emit error(i18n("Unrecognized line in config: \"%1\"", line), true);
+            emit error(i18n("Unrecognized line in config: \'%1\'", line), true);
             success = false;
         }
     }
@@ -398,13 +395,13 @@ void Loader::parseConfigLine(const QString &line, void (PwmFan::*memberSetFuncti
                 if (pwmFanPointer)
                     (pwmFanPointer->*memberSetFunction)(value);
                 else
-                    emit error(i18n("Invalid fan entry: \"%1\"", pwmFanString), true);
+                    emit error(i18n("Invalid fan entry: \'%1\'", pwmFanString), true);
             }
             else
                 emit error(i18n("%1 is not an integer!", valueString));
         }
         else
-            emit error(i18n("Invalid entry to parse: \"%1\"", entry));
+            emit error(i18n("Invalid entry to parse: \'%1\'", entry));
     }
 }
 
@@ -412,10 +409,7 @@ bool Loader::load(const QUrl &url)
 {
     QString fileName;
     if (url.isEmpty())
-    {
-//        qDebug() << "Given empty url. Fallback to" << m_configUrl;
         fileName = m_configUrl.toLocalFile();
-    }
     else if (url.isValid())
     {
         if (url.isLocalFile())
@@ -423,15 +417,16 @@ bool Loader::load(const QUrl &url)
 
         else
         {
-            emit error(i18n("%1 is not a local file!", url.toDisplayString()));
+            emit error(i18n("\'%1\' is not a local file!", url.toDisplayString()));
             return false;
         }
     }
     else
     {
-        emit error(i18n("%1 is not a valid url!", url.toDisplayString()));
+        emit error(i18n("\'%1\' is not a valid url!", url.toDisplayString()));
         return false;
     }
+    emit info(i18n("Loading config file: \'%1\'", fileName));
 
     QTextStream stream;
     QFile file(fileName);
@@ -440,7 +435,6 @@ bool Loader::load(const QUrl &url)
     {
         stream.setDevice(&file);
         m_configFileContent = stream.readAll();
-        emit configChanged();
     }
     else if (file.exists())
     {
@@ -457,7 +451,7 @@ bool Loader::load(const QUrl &url)
             {
                 if (reply->error() == 4)
                 {
-                    qDebug() << "Loading of file aborted by user";
+                    emit info(i18n("Loading of file aborted by user"));
                     return false;
                 }
 
@@ -465,17 +459,14 @@ bool Loader::load(const QUrl &url)
                 return false;
             }
             else
-            {
                 m_configFileContent = reply->data().value(QStringLiteral("content")).toString();
-                emit configChanged();
-            }
         }
         else
             emit error(i18n("Action not supported! Try running the application as root."), true);
     }
     else
     {
-        emit error(i18n("File does not exist: \"%1\"" ,fileName));
+        emit error(i18n("File does not exist: \'%1\'" ,fileName));
         return false;
     }
 
@@ -507,12 +498,26 @@ bool Loader::save(const QUrl &url)
     }
     else
     {
-        emit error(i18n("%1 is not a local file!", url.toDisplayString()), true);
+        emit error(i18n("\'%1\' is not a local file!", url.toDisplayString()), true);
         return false;
     }
-
     QFile file(fileName);
 
+    if (file.open(QFile::ReadOnly | QFile::Text))
+    {
+        QTextStream stream(&file);
+        QString fileContent = stream.readAll();
+
+        if (m_config == fileContent)
+        {
+            emit info(i18n("No changes made to config"));
+            return false;
+        }
+        else
+            file.close();
+    }
+
+    emit info(i18n("Saving config to \'%1\'", fileName));
     if (file.open(QFile::WriteOnly | QFile::Text))
     {
         QTextStream stream(&file);
@@ -536,7 +541,7 @@ bool Loader::save(const QUrl &url)
             {
                 if (reply->error() == 4)
                 {
-                    qDebug() << "Saving of file aborted by user";
+                    emit info(i18n("Saving of file aborted by user"));
                     return false;
                 }
 
@@ -565,6 +570,7 @@ void Loader::updateConfig()
     {
         m_config = config;
         emit configChanged();
+        emit needsSaveChanged();
     }
 }
 
@@ -736,6 +742,7 @@ void Loader::abortTestingFans()
 
 void Loader::detectSensors()
 {
+    emit info(i18n("Start detecting Sensors"));
     auto program = QStringLiteral("sensors-detect");
     auto arguments = QStringList() << QStringLiteral("--auto");
 
@@ -784,6 +791,8 @@ void Loader::handleDetectSensorsResult(int exitCode)
 
     if (process)
         process->deleteLater();
+
+    emit info(i18n("Finished detecting Sensors"));
 }
 
 void Loader::handleDetectSensorsResult(KJob *job)
@@ -808,6 +817,8 @@ void Loader::handleDetectSensorsResult(KJob *job)
 
         parseHwmons();
     }
+
+    emit info(i18n("Finished detecting Sensors"));
 }
 
 QList<QObject *> Loader::hwmonsAsObjects() const
@@ -847,10 +858,9 @@ void Loader::setRestartServiceAfterTesting(bool restart)
     emit restartServiceAfterTestingChanged();
 }
 
-void Loader::reset() const
+void Loader::toDefault()
 {
     for (const auto &hwmon : m_hwmons)
-        hwmon->reset();
-}
-
+        hwmon->toDefault();
+    }
 }

+ 5 - 3
import/src/loader.h

@@ -51,7 +51,7 @@ class Loader : public QObject
     Q_PROPERTY(int interval READ interval WRITE setInterval NOTIFY intervalChanged)
     Q_PROPERTY(bool sensorsDetected READ sensorsDetected NOTIFY sensorsDetectedChanged)
     Q_PROPERTY(bool restartServiceAfterTesting READ restartServiceAfterTesting WRITE setRestartServiceAfterTesting NOTIFY restartServiceAfterTestingChanged)
-    Q_PROPERTY(bool configEqualToLoadedFile READ configEqualToLoadedFile NOTIFY configChanged)
+    Q_PROPERTY(bool needsSave READ needsSave NOTIFY needsSaveChanged)
 
 
 public:
@@ -80,8 +80,8 @@ public:
     PwmFan *pwmFan(int hwmonIndex, int pwmFanIndex) const;
     Temp *temp(int hwmonIndex, int tempIndex) const;
     Fan *fan(int hwmonIndex, int fanIndex) const;
-    void reset() const;
-    bool configEqualToLoadedFile() const { return m_config == m_configFileContent; }
+    void toDefault();
+    bool needsSave() const { return m_config != m_configFileContent; }
 
 
 public slots:
@@ -120,11 +120,13 @@ signals:
     void hwmonsChanged();
     void intervalChanged();
     void error(QString, bool = false);
+    void info(QString);
     void sensorsUpdateNeeded();
     void invalidConfigUrl();
     void sensorsDetectedChanged();
     void restartServiceAfterTestingChanged();
     void requestSetServiceActive(bool);
+    void needsSaveChanged();
 };
 
 }

+ 7 - 7
import/src/pwmfan.cpp

@@ -88,7 +88,7 @@ PwmFan::PwmFan(uint index, Hwmon *parent) : Fan(index, parent),
             }
             else
             {
-                emit error(i18n("Can't open pwm file: \"%1\"", pwmFile->fileName()));
+                emit error(i18n("Can't open pwm file: \'%1\'", pwmFile->fileName()));
                 delete pwmFile;
             }
 
@@ -106,7 +106,7 @@ PwmFan::PwmFan(uint index, Hwmon *parent) : Fan(index, parent),
             }
             else
             {
-                emit error(i18n("Can't open pwm_enable file: \"%1\"", pwmEnableFile->fileName()));
+                emit error(i18n("Can't open pwm_enable file: \'%1\'", pwmEnableFile->fileName()));
                 delete pwmEnableFile;
             }
         }
@@ -134,9 +134,9 @@ void PwmFan::update()
     setPwmEnable(m_enableStream->readAll().toInt(), false);
 }
 
-void PwmFan::reset()
+void PwmFan::toDefault()
 {
-    Fan::reset();
+    Fan::toDefault();
 
     setHasTemp(false);
     setTemp(Q_NULLPTR);
@@ -180,7 +180,7 @@ void PwmFan::reset()
         }
         else
         {
-            emit error(i18n("Can't open pwm file: \"%1\"", pwmFile->fileName()));
+            emit error(i18n("Can't open pwm file: \'%1\'", pwmFile->fileName()));
             delete pwmFile;
         }
 
@@ -198,7 +198,7 @@ void PwmFan::reset()
         }
         else
         {
-            emit error(i18n("Can't open pwm_enable file: \"%1\"", pwmEnableFile->fileName()));
+            emit error(i18n("Can't open pwm_enable file: \'%1\'", pwmEnableFile->fileName()));
             delete pwmEnableFile;
         }
     }
@@ -426,7 +426,7 @@ void PwmFan::continueTest()
                 emit testStatusChanged();
                 return;
             }
-            
+
             setPwm(qMax(0, (int)qMin(m_pwm * 0.95, m_pwm - 5.0)));
             m_zeroRpm = 0;
         }

+ 1 - 1
import/src/pwmfan.h

@@ -93,7 +93,7 @@ public:
     void setMinStop(int minStop) { if (minStop != m_minStop) { m_minStop = minStop; emit minStopChanged(); } }
     bool setPwmEnable(int pwmEnable, bool write = true);
     void setActive(bool active);
-    void reset() Q_DECL_OVERRIDE;
+    void toDefault() Q_DECL_OVERRIDE;
     bool isValid() const Q_DECL_OVERRIDE;
     Q_INVOKABLE void test();
     Q_INVOKABLE void abortTest();

+ 1 - 1
import/src/sensor.h

@@ -44,7 +44,7 @@ public:
 
     virtual QString name() const = 0;
     virtual void setName(const QString &name) = 0;
-    virtual void reset() = 0;
+    virtual void toDefault() = 0;
     virtual bool isValid() const = 0;
     QString path() const { return m_path; }
     Hwmon * parent() const { return m_parent; }

+ 118 - 92
import/src/systemdcommunicator.cpp

@@ -26,6 +26,7 @@
 #include <QtCore/QTimer>
 #include <QtDBus/QDBusArgument>
 #include <QtDBus/QDBusInterface>
+#include <QtDBus/QDBusReply>
 
 #include <KAuth/KAuthExecuteJob>
 #include <KI18n/KLocalizedString>
@@ -75,7 +76,10 @@ SystemdCommunicator::SystemdCommunicator(GUIBase *parent, const QString &service
     m_serviceInterface(Q_NULLPTR)
 {
     if (parent)
+    {
         connect(this, &SystemdCommunicator::error, parent, &GUIBase::handleError);
+        connect(this, &SystemdCommunicator::info, parent, &GUIBase::handleInfo);
+    }
 
     if (serviceName.isEmpty())
         setServiceName(QStringLiteral(STANDARD_SERVICE_NAME));
@@ -83,8 +87,12 @@ SystemdCommunicator::SystemdCommunicator(GUIBase *parent, const QString &service
         setServiceName(serviceName);
 
     emit serviceNameChanged();
-    emit serviceEnabledChanged();
+
+    m_serviceActive = systemdServiceActive();
     emit serviceActiveChanged();
+
+    m_serviceEnabled = systemdServiceEnabled();
+    emit serviceEnabledChanged();
 }
 
 void SystemdCommunicator::setServiceName(const QString &name)
@@ -136,10 +144,12 @@ void SystemdCommunicator::setServiceName(const QString &name)
         emit serviceNameChanged();
         emit serviceEnabledChanged();
         emit serviceActiveChanged();
+
+        emit info(i18n("New srevice name: \'%1\'", m_serviceName));
     }
 }
 
-bool SystemdCommunicator::serviceExists()
+bool SystemdCommunicator::serviceExists() const
 {
     if (m_serviceInterface)
     {
@@ -165,11 +175,11 @@ bool SystemdCommunicator::serviceExists()
             return true;
     }
 
-    emit error(i18n("Service does not exist: \"%1\"", m_serviceName));
+    emit error(i18n("Service does not exist: \'%1\'", m_serviceName));
     return false;
 }
 
-bool SystemdCommunicator::serviceActive()
+bool SystemdCommunicator::systemdServiceActive() const
 {
     if (serviceExists() && m_serviceInterface)
     {
@@ -179,67 +189,58 @@ bool SystemdCommunicator::serviceActive()
     return false;
 }
 
-bool SystemdCommunicator::serviceEnabled()
+bool SystemdCommunicator::systemdServiceEnabled() const
 {
-    if (serviceExists() && m_serviceInterface)
+    if (serviceExists())
     {
-        if (m_serviceInterface->property("UnitFileState").toString() == QStringLiteral("enabled"))
-            return true;
-
+        QDBusReply<QString> reply = m_managerInterface->call(QDBus::AutoDetect, QStringLiteral("GetUnitFileState"), m_serviceName + ".service");
+        if (reply.isValid())
+            return reply.value() == QStringLiteral("enabled");
+        else
+            emit error(reply.error().message());
     }
     return false;
 }
 
-bool SystemdCommunicator::setServiceEnabled(bool enabled)
+void SystemdCommunicator::setServiceEnabled(bool enabled)
 {
-    if (serviceExists())
-    {
-        if (enabled != serviceEnabled())
-        {
-            const auto action = enabled ? QStringLiteral("EnableUnitFiles") : QStringLiteral("DisableUnitFiles");
-            const auto files = QStringList() << m_serviceName + ".service";
-            auto arguments = QVariantList() << files << false;
-            if (enabled)
-                arguments << true;
+    if (m_serviceEnabled == enabled)
+        return;
 
-            if (dbusAction(action, arguments))
-            {
-                if (dbusAction(QStringLiteral("Reload")))
-                {
-                    emit serviceEnabledChanged();
-                    return true;
-                }
-            }
-            return false;
-        }
-        return true;
-    }
-    return false;
+    m_serviceEnabled = enabled;
+    emit serviceEnabledChanged();
+    emit needsApplyChanged();
 }
 
-bool SystemdCommunicator::setServiceActive(bool active)
+void SystemdCommunicator::setServiceActive(bool active)
 {
-//    qDebug() << "Set service active:" << active;
+    if (m_serviceActive == active)
+        return;
 
+    m_serviceActive = active;
+    emit serviceActiveChanged();
+    emit needsApplyChanged();
+}
+
+bool SystemdCommunicator::restartService()
+{
     if (serviceExists())
     {
-        if (active != serviceActive())
-        {
-            auto args = QVariantList() << m_serviceName + ".service" << "replace";
-            const auto action = active ? QStringLiteral("ReloadOrRestartUnit") : QStringLiteral("StopUnit");
-
-            if (dbusAction(action, args))
-            {
-                emit serviceActiveChanged();
-                return true;
-            }
-        }
+        emit info(i18n("Restarting service: \'%1\'", m_serviceName));
 
-        return true;
+        auto args = QVariantList() << m_serviceName + ".service" << "replace";
+        return dbusAction(QStringLiteral("ReloadOrRestartUnit"), args);
     }
+
+    emit error(i18n("Service does not exist: \'%1\'", m_serviceName));
     return false;
 }
 
+bool SystemdCommunicator::needsApply() const
+{
+    return m_serviceActive != systemdServiceActive() || m_serviceEnabled != systemdServiceEnabled();
+}
+
 bool SystemdCommunicator::dbusAction(const QString &method, const QVariantList &arguments)
 {
     if (!m_managerInterface->isValid())
@@ -249,76 +250,101 @@ bool SystemdCommunicator::dbusAction(const QString &method, const QVariantList &
 
     }
 
-    const auto dbusreply = arguments.isEmpty() ? m_managerInterface->call(QDBus::AutoDetect, method) : m_managerInterface->callWithArgumentList(QDBus::AutoDetect, method, arguments);
-
-    if (dbusreply.type() != QDBusMessage::ErrorMessage)
-        return true;
+    auto action = newFancontrolAction();
+    QVariantMap map;
+    map[QStringLiteral("action")] = "dbusaction";
+    map[QStringLiteral("method")] = method;
+    map[QStringLiteral("arguments")] = arguments;
+    action.setArguments(map);
 
-    if (dbusreply.errorMessage() == QStringLiteral("Interactive authentication required."))
+    const auto job = action.execute();
+    bool success = job->exec();
+    if (success)
+    {
+        if (method == QStringLiteral("EnableUnitFiles") || method == QStringLiteral("DisableUnitFiles"))
+        {
+            emit serviceEnabledChanged();
+            emit needsApplyChanged();
+        }
+        else if (method == QStringLiteral("StartUnit") || method == QStringLiteral("StopUnit"))
+        {
+            emit serviceActiveChanged();
+            emit needsApplyChanged();
+        }
+    }
+    else
     {
-        auto action = newFancontrolAction();
-        QVariantMap map;
-        map[QStringLiteral("action")] = "dbusaction";
-        map[QStringLiteral("method")] = method;
-        map[QStringLiteral("arguments")] = arguments;
-        action.setArguments(map);
-
-        const auto job = action.execute();
-        connect(job, &KAuth::ExecuteJob::result, this, &SystemdCommunicator::handleDbusActionResult);
-        job->start();
-
-        return true;
+        emit error(i18n("Dbus error: %1", job->errorString()));
     }
-
-    emit error(dbusreply.errorMessage());
-    return false;
-
+    return success;
 }
 
-void SystemdCommunicator::handleDbusActionResult(KJob *job)
+void SystemdCommunicator::apply(bool serviceRestart)
 {
-    if (job->error())
+    if (serviceExists())
     {
-        if (job->error() == KAuth::ActionReply::HelperBusyError)
+        if (m_serviceEnabled != systemdServiceEnabled())
         {
-//            qDebug() << "Helper busy...";
-
-            const auto executeJob = static_cast<KAuth::ExecuteJob *>(job);
-            if (executeJob)
+            QString method;
+            if (m_serviceEnabled)
             {
-                const auto newJob = executeJob->action().execute();
-                connect(newJob, &KAuth::ExecuteJob::result, this, &SystemdCommunicator::handleDbusActionResult);
+                emit info(i18n("Enabling service autostart at boot:\'%1\'", m_serviceName));
+                method = QStringLiteral("EnableUnitFiles");
+            }
+            else
+            {
+                emit info(i18n("Disabling service autostart at boot: \'%1\'", m_serviceName));
+                method = QStringLiteral("DisableUnitFiles");
+            }
+            const auto files = QStringList() << m_serviceName + ".service";
+            auto args = QVariantList() << files << false;
+            if (m_serviceEnabled)
+                args << true;
 
-                QTimer::singleShot(50, newJob, &KAuth::ExecuteJob::start);
+            if (!dbusAction(method, args))
                 return;
-            }
         }
 
-        emit error(job->errorText());
+        if (m_serviceActive != systemdServiceActive())
+        {
+            QString method;
+            if (m_serviceEnabled)
+            {
+                emit info(i18n("Starting service: \'%1\'", m_serviceName));
+                method = QStringLiteral("StartUnit");
+            }
+            else
+            {
+                emit info(i18n("Stopping service: \'%1\'", m_serviceName));
+                method = QStringLiteral("StopUnit");
+            }
+            auto args = QVariantList() << m_serviceName + ".service" << "replace";
+
+            if (!dbusAction(method, args))
+                return;
+        }
+        else if (systemdServiceActive() && m_serviceActive && serviceRestart)
+            restartService();
     }
 }
 
-bool SystemdCommunicator::restartService()
+void SystemdCommunicator::reset()
 {
-    if (serviceExists())
-    {
-        QVariantList args;
-        args << m_serviceName + ".service" << "replace";
-
-        return dbusAction(QStringLiteral("ReloadOrRestartUnit"), args);
-    }
-
-    emit error(i18n("Service does not exist: \"%1\"", m_serviceName));
-    return false;
+    setServiceActive(systemdServiceActive());
+    setServiceEnabled(systemdServiceEnabled());
 }
 
 void SystemdCommunicator::updateServiceProperties(QString, QVariantMap propchanged, QStringList)
 {
     if (propchanged.value(QStringLiteral("ActiveState")).isValid())
-        emit serviceActiveChanged();
+    {
+        emit needsApplyChanged();
+    }
 
     if (propchanged.value(QStringLiteral("UnitFileState")).isValid())
-        emit serviceEnabledChanged();
+    {
+        emit needsApplyChanged();
+    }
 }
 
 }

+ 16 - 7
import/src/systemdcommunicator.h

@@ -41,6 +41,7 @@ class SystemdCommunicator : public QObject
     Q_PROPERTY(bool serviceExists READ serviceExists NOTIFY serviceNameChanged)
     Q_PROPERTY(bool serviceEnabled READ serviceEnabled WRITE setServiceEnabled NOTIFY serviceEnabledChanged)
     Q_PROPERTY(bool serviceActive READ serviceActive WRITE setServiceActive NOTIFY serviceActiveChanged)
+    Q_PROPERTY(bool needsApply READ needsApply NOTIFY needsApplyChanged)
 
 public:
 
@@ -48,12 +49,15 @@ public:
 
     QString serviceName() const { return m_serviceName; }
     void setServiceName(const QString &name);
-    bool serviceExists();
-    bool serviceEnabled();
-    bool serviceActive();
-    bool setServiceEnabled(bool enabled);
-    bool setServiceActive(bool active);
+    bool serviceExists() const;
+    bool serviceEnabled() const { return m_serviceEnabled; }
+    bool serviceActive() const { return m_serviceActive; }
+    void setServiceEnabled(bool enabled);
+    void setServiceActive(bool active);
+    bool needsApply() const;
     Q_INVOKABLE bool restartService();
+    Q_INVOKABLE void apply(bool serviceRestart = false);
+    Q_INVOKABLE void reset();
 
 
 signals:
@@ -61,18 +65,21 @@ signals:
     void serviceNameChanged();
     void serviceEnabledChanged();
     void serviceActiveChanged();
-    void error(QString, bool = false);
+    void needsApplyChanged();
+    void error(QString, bool = false) const;
+    void info(QString) const;
 
 
 protected slots:
 
     void updateServiceProperties(QString, QVariantMap, QStringList);
-    void handleDbusActionResult(KJob *job);
 
 
 protected:
 
     bool dbusAction(const QString &method, const QVariantList &arguments = QVariantList());
+    bool systemdServiceActive() const;
+    bool systemdServiceEnabled() const;
 
 
 private:
@@ -81,6 +88,8 @@ private:
     QString m_serviceObjectPath;
     QDBusInterface * const m_managerInterface;
     QDBusInterface *m_serviceInterface;
+    bool m_serviceEnabled;
+    bool m_serviceActive;
 };
 
 }

+ 6 - 6
import/src/temp.cpp

@@ -58,7 +58,7 @@ Temp::Temp(uint index, Hwmon *parent) :
         else
         {
             delete valueFile;
-            emit error(i18n("Can't open value file: \"%1\"", parent->path() + "/temp" + QString::number(index) + "_input"));
+            emit error(i18n("Can't open value file: \'%1\'", parent->path() + "/temp" + QString::number(index) + "_input"));
         }
 
         if (labelFile->exists())
@@ -66,10 +66,10 @@ Temp::Temp(uint index, Hwmon *parent) :
             if (labelFile->open(QFile::ReadOnly))
                 m_label = QTextStream(labelFile).readLine();
             else
-                emit error(i18n("Can't open label file: \"%1\"", parent->path() + "/temp" + QString::number(index) + "_label"));
+                emit error(i18n("Can't open label file: \'%1\'", parent->path() + "/temp" + QString::number(index) + "_label"));
         }
         else
-            emit error(i18n("Temp has no label: \"%1\"", parent->path() + "/temp" + QString::number(index)));
+            emit error(i18n("Temp has no label: \'%1\'", parent->path() + "/temp" + QString::number(index)));
 
         delete labelFile;
     }
@@ -111,7 +111,7 @@ void Temp::setName(const QString &name)
     }
 }
 
-void Temp::reset()
+void Temp::toDefault()
 {
     if (m_valueStream->device() && m_parent)
     {
@@ -130,7 +130,7 @@ void Temp::reset()
                 m_value /= 1000;
             }
             else
-                emit error(i18n("Can't open value file: \"%1\"", m_parent->path() + "/temp" + QString::number(m_index) + "_input"));
+                emit error(i18n("Can't open value file: \'%1\'", m_parent->path() + "/temp" + QString::number(m_index) + "_input"));
         }
     }
 }
@@ -143,7 +143,7 @@ void Temp::update()
     const auto value = m_valueStream->readAll().toInt(&success) / 1000;
 
     if (!success)
-        emit error(i18n("Can't update value of temp: \"%1\"", m_parent->path() + "/temp" + QString::number(m_index)));
+        emit error(i18n("Can't update value of temp: \'%1\'", m_parent->path() + "/temp" + QString::number(m_index)));
 
     if (value != m_value)
     {

+ 1 - 1
import/src/temp.h

@@ -48,7 +48,7 @@ public:
     int value() const { return m_value; }
     QString name() const Q_DECL_OVERRIDE;
     void setName(const QString &name) Q_DECL_OVERRIDE;
-    void reset() Q_DECL_OVERRIDE;
+    void toDefault() Q_DECL_OVERRIDE;
     bool isValid() const Q_DECL_OVERRIDE;
 
 

+ 25 - 20
kcm/package/contents/ui/KCM.qml

@@ -39,33 +39,19 @@ Item {
     implicitWidth: 1024
     implicitHeight: 768
 
-    Connections {
-        target: loader
-        onConfigFileChanged: kcm.needsSave = true
-    }
-
     Connections {
         target: Fancontrol.base
-        onMinTempChanged: kcm.needsSave = true
-        onMaxTempChanged: kcm.needsSave = true
-        onServiceNameChanged: kcm.needsSave = true
-        onConfigUrlChanged: kcm.needsSave = true
+        onNeedsApplyChanged: kcm.needsSave = Fancontrol.base.needsApply
     }
 
     Connections {
         target: kcm
         onAboutToSave: {
-            Fancontrol.base.save(true);
-            if (systemdCom.serviceActive && enabledBox.checked) {
-                systemdCom.restartService();
-            } else {
-                systemdCom.serviceActive = enabledBox.checked;
-            }
-            systemdCom.serviceEnabled = enabledBox.checked;
+            Fancontrol.base.apply();
         }
         onAboutToLoad: {
             Fancontrol.base.load();
-            enabledBox.checked = systemdCom.serviceEnabled && systemdCom.serviceActive;
+            enabledBox.checked = systemdCom.serviceActive;
         }
         onAboutToDefault: enabledBox.checked = false
     }
@@ -100,9 +86,15 @@ Item {
         visible: pwmFans.length > 0
         text: i18n("Control fans manually")
         checked: systemdCom.serviceEnabled && systemdCom.serviceActive;
-        onCheckedChanged: if (checked !== systemdCom.serviceActive || checked !== systemdCom.serviceEnabled) {
-            kcm.needsSave = true;
+        onCheckedChanged: {
+            systemdCom.serviceActive = enabledBox.checked;
             loader.restartServiceAfterTesting = checked;
+            autostartBox.checked = true;
+        }
+
+        Connections {
+            target: systemdCom
+            onServiceActiveChanged: if (systemdCom.serviceActive != enabledBox.checked) enabledBox.checked = systemdCom.serviceActive
         }
     }
 
@@ -115,6 +107,19 @@ Item {
         anchors.bottomMargin: advancedButton.height / 4
         visible: enabledBox.checked
 
+        CheckBox {
+            id: autostartBox
+
+            text: i18n("Enable service at boot")
+            checked: systemdCom.serviceEnabled
+            onCheckedChanged: systemdCom.serviceEnabled = checked
+
+            Connections {
+                target: systemdCom
+                onServiceEnabledChanged: if (systemdCom.serviceEnabled != autostartBox.checked) autostartBox.checked = systemdCom.serviceEnabled
+            }
+        }
+
         RowLayout {
             visible: enabledBox.checked && pwmFans.length > 0
 
@@ -312,7 +317,7 @@ Item {
             Fancontrol.OptionInput {
                 Layout.minimumWidth: implicitWidth
                 Layout.fillWidth: true
-                text: Fancontrol.base.configUrl.toString().replace("file://", "")
+                value: Fancontrol.base.configUrl.toString().replace("file://", "")
                 color: Fancontrol.base.configValid ? "green" : "red"
                 onTextChanged: Fancontrol.base.configUrl = text
             }