Преглед изворни кода

Huge changes all over the place

Malte Veerman пре 10 година
родитељ
комит
3adc0052cf
42 измењених фајлова са 3968 додато и 170 уклоњено
  1. 71 46
      CMakeLists.txt
  2. 17 11
      fancontrol-gui/CMakeLists.txt
  3. 10 0
      fancontrol-gui/metadata.desktop
  4. 39 0
      fancontrol-gui/src/gui.cpp
  5. 67 0
      fancontrol-gui/src/gui.h
  6. 27 17
      fancontrol-gui/src/main.cpp
  7. 7 9
      helper/CMakeLists.txt
  8. 9 0
      helper/fancontrol_gui.actions
  9. 1 0
      helper/src/helper.h
  10. 46 0
      icon.svg
  11. 14 0
      kcm/CMakeLists.txt
  12. 14 0
      kcm/kcm_fancontrol.desktop
  13. 86 0
      kcm/src/fancontrolkcm.cpp
  14. 80 0
      kcm/src/fancontrolkcm.h
  15. 35 0
      lib/CMakeLists.txt
  16. 110 0
      lib/src/hwmon.cpp
  17. 87 0
      lib/src/hwmon.h
  18. 451 0
      lib/src/loader.cpp
  19. 92 0
      lib/src/loader.h
  20. 377 0
      lib/src/sensors.cpp
  21. 231 0
      lib/src/sensors.h
  22. 280 0
      lib/src/systemdcommunicator.cpp
  23. 93 0
      lib/src/systemdcommunicator.h
  24. 60 0
      package/contents/scripts/arrayfunctions.js
  25. 38 0
      package/contents/scripts/coordinates.js
  26. 27 0
      package/contents/scripts/math.js
  27. 37 0
      package/contents/scripts/units.js
  28. 206 0
      package/contents/ui/Application.qml
  29. 54 0
      package/contents/ui/ConfigfileTab.qml
  30. 80 0
      package/contents/ui/KCM.qml
  31. 65 0
      package/contents/ui/OptionInput.qml
  32. 441 0
      package/contents/ui/PwmFan.qml
  33. 63 0
      package/contents/ui/PwmFansTab.qml
  34. 77 0
      package/contents/ui/PwmPoint.qml
  35. 102 0
      package/contents/ui/SensorsTab.qml
  36. 180 0
      package/contents/ui/SettingsTab.qml
  37. 73 0
      package/contents/ui/ToolTip.qml
  38. 16 0
      package/metadata.desktop
  39. BIN
      po/de_DE.mo
  40. 82 47
      po/de_DE.po
  41. 48 0
      po/extract_messages.sh
  42. 75 40
      po/fancontrol-gui.pot

+ 71 - 46
CMakeLists.txt

@@ -1,86 +1,111 @@
-cmake_minimum_required(VERSION 3.0.0)
+cmake_minimum_required(VERSION 3.0.2)
 
 project(fancontroller)
 
-include(FeatureSummary)
-
-option(NO_SYSTEMD "Compile without Systemd support. Reduces functionality significantly!")
-option(BUILD_CORE "Compile the shared library")
-option(BUILD_GUI "Compile the GUI")
 
-#Find Qt5
-find_package(Qt5 COMPONENTS Widgets Qml Quick REQUIRED)
-include_directories(${Qt5Widgets_INCLUDE_DIRS}
-                    ${Qt5Qml_INCLUDE_DIRS}
-                    ${Qt5Quick_INCLUDE_DIRS}
-                    ${CMAKE_CURRENT_BINARY_DIR})
-add_definitions(${Qt5Widgets_DEFINITIONS})
+#options
+option(NO_SYSTEMD "Compile without Systemd support. Reduces functionality significantly!" OFF)
+option(BUILD_GUI "Build the standalone application" ON)
+option(BUILD_KCM "Build the KCM" OFF)
+option(INSTALL_SHARED "Install the shared parts" ON)
 
-set(CMAKE_AUTOMOC ON)
 
-# Silence a warning
+#Silence warnings
 cmake_policy(SET CMP0037 OLD)
+cmake_policy(SET CMP0063 NEW)
 
-#Find KF5
+
+#Find ECM
 find_package(ECM REQUIRED)
 set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR})
 
-find_package(KF5 COMPONENTS Auth Config I18n Declarative REQUIRED)
-find_package(Qt5DBus)
 
-feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES)
+#Find KF5
+find_package(KF5 COMPONENTS I18n Package REQUIRED)
 
+
+#includes 
+include(GenerateExportHeader)
 include(KDEInstallDirs)
 include(KDECMakeSettings)
 include(KDECompilerSettings)
+include(FeatureSummary)
 
-#Systemd
-find_library(SYSTEMD_FOUND NAMES systemd)
 
-if(NOT NO_SYSTEMD AND SYSTEMD_FOUND AND Qt5DBus_FOUND)
+#Systemd
+if(NOT NO_SYSTEMD)
 
     message(STATUS "Compiling for Systemd")
-
+    find_package(Qt5DBus REQUIRED)
     include_directories(${Qt5DBus_INCLUDE_DIRS})
 
-else(NOT NO_SYSTEMD AND SYSTEMD_FOUND AND Qt5DBus_FOUND)
+else(NOT NO_SYSTEMD)
 
     message(STATUS "Compiling without Systemd")
     set(NO_SYSTEMD true)
+    add_definitions(-DNO_SYSTEMD)
 
-endif(NOT NO_SYSTEMD AND SYSTEMD_FOUND AND Qt5DBus_FOUND)
+endif(NOT NO_SYSTEMD)
 
-if(BUILD_CORE)
 
-    message(STATUS "Compile core")
-    add_subdirectory(helper)
-    add_subdirectory(share)
-    
-endif(BUILD_CORE)
+#Shared library
+add_subdirectory(lib)
+
+
+#KHelper for actions that require superuser rights
+add_subdirectory(helper)
+
 
+#Build the standalone application
 if(BUILD_GUI)
 
-    message(STATUS "Compile GUI")
+    message(STATUS "Build the standalone application")
     add_subdirectory(fancontrol-gui)
     
 endif(BUILD_GUI)
 
-#translations
-FILE(GLOB MO_FILES po/*.mo)
 
-set(catalogname fancontrol-gui)
+#Build the KCM
+if(BUILD_KCM)
 
-FOREACH(MO_FILE ${MO_FILES})
+    message(STATUS "Build the KCM")
+    add_subdirectory(kcm)
     
-    GET_FILENAME_COMPONENT(_moFileName ${MO_FILE} NAME)
-    STRING(REGEX REPLACE "^${catalogname}_?" "" _langCode ${_moFileName} )
-    STRING(REGEX REPLACE "\\.mo$" "" _langCode ${_langCode} )
+endif(BUILD_KCM)
+
+
+#summary
+feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES)
+
+
+#install the shared parts
+if(INSTALL_SHARED)
+
+    #KPackage containing the QML and javascript files
+    kpackage_install_package(package kcm_fancontrol kcms)
 
-    IF( _langCode )
-    
-        INSTALL(FILES ${MO_FILE} DESTINATION ${LOCALE_INSTALL_DIR}/${_langCode}/LC_MESSAGES/ RENAME ${catalogname}.mo)
-        LIST(APPEND GMO_FILES ${_gmoFile})
-    
-    ENDIF( _langCode )
 
-ENDFOREACH(MO_FILE ${MO_FILES})
+    #icon
+    install(FILES icon.svg RENAME "fancontrol_gui.svg" DESTINATION "${ICON_INSTALL_DIR}/hicolor/scalable/apps")
+
+
+    #translations
+    FILE(GLOB MO_FILES po/*.mo)
+
+    set(catalogname kcm_fancontrol)
+
+    foreach(MO_FILE ${MO_FILES})
+	
+	GET_FILENAME_COMPONENT(_moFileName ${MO_FILE} NAME)
+	STRING(REGEX REPLACE "^${catalogname}_?" "" _langCode ${_moFileName} )
+	STRING(REGEX REPLACE "\\.mo$" "" _langCode ${_langCode} )
+
+	if(_langCode)
+	
+	    install(FILES ${MO_FILE} DESTINATION ${LOCALE_INSTALL_DIR}/${_langCode}/LC_MESSAGES/ RENAME ${catalogname}.mo)
+	
+	endif(_langCode)
+
+    endforeach(MO_FILE ${MO_FILES})
+    
+endif(INSTALL_SHARED)

+ 17 - 11
fancontrol-gui/CMakeLists.txt

@@ -1,14 +1,20 @@
-set(Fancontrol-GUI_SRCS src/main.cpp)
+set(Fancontrol_GUI_SRCS src/main.cpp
+                        src/gui.cpp)
 
-set(LIBRARIES fancontrol-gui-share
-              KF5::Declarative
-              KF5::I18n)
+set(LIBRARIES fancontrol_gui_lib
+	      Qt5::Widgets
+	      KF5::Declarative
+              KF5::I18n
+              KF5::Package)
+              
+find_package(Qt5Widgets REQUIRED)
+find_package(KF5Declarative REQUIRED)
 
-add_executable(fancontrol-gui ${Fancontrol-GUI_SRCS} ${RESOURCES})
-target_link_libraries(fancontrol-gui ${LIBRARIES})
-set_property(TARGET fancontrol-gui PROPERTY CXX_STANDARD 11)
+include_directories(${Qt5Widgets_INCLUDE_DIRS})
+add_definitions(${Qt5Widgets_DEFINITIONS})
 
-install(TARGETS fancontrol-gui RUNTIME DESTINATION ${BIN_INSTALL_DIR})
-install(FILES other/fancontrol-gui.desktop DESTINATION "${SHARE_INSTALL_PREFIX}/applications")
-install(FILES other/fancontrol-gui.svg DESTINATION "${ICON_INSTALL_DIR}/hicolor/scalable/apps")
-install(FILES qml/fancontrol-gui.qml DESTINATION "${SHARE_INSTALL_PREFIX}/fancontrol-gui/qml")
+add_executable(fancontrol_gui ${Fancontrol_GUI_SRCS})
+target_link_libraries(fancontrol_gui ${LIBRARIES})
+
+install(TARGETS fancontrol_gui RUNTIME DESTINATION ${BIN_INSTALL_DIR})
+install(FILES metadata.desktop RENAME "fancontrol_gui.desktop" DESTINATION ${XDG_APPS_INSTALL_DIR})

+ 10 - 0
fancontrol-gui/metadata.desktop

@@ -0,0 +1,10 @@
+[Desktop Entry]
+Type=Application
+Version=1.0
+Name=Fancontrol-GUI
+Comment=GUI for the fancontrol script
+Exec=fancontrol_gui
+Icon=fancontrol_gui
+Terminal=false
+Categories=System;Settings;
+Keywords=pwmconfig;

+ 39 - 0
fancontrol-gui/src/gui.cpp

@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2015  Malte Veerman <maldela@halloarsch.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include "gui.h"
+#include "../lib/src/sensors.h"
+#include "../lib/src/hwmon.h"
+
+#include <QtQml>
+
+GUI::GUI(QObject *parent) : QObject(parent)
+{
+    m_loader->load(QUrl::fromLocalFile("/etc/fancontrol"));
+    
+    qmlRegisterType<Loader>();
+    qmlRegisterType<Hwmon>();
+    qmlRegisterType<Fan>();
+    qmlRegisterType<PwmFan>();
+    qmlRegisterType<Temp>();
+    
+#ifndef NO_SYSTEMD
+    qmlRegisterType<SystemdCommunicator>();
+#endif
+}

+ 67 - 0
fancontrol-gui/src/gui.h

@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2015  Malte Veerman <maldela@halloarsch.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#ifndef GUI_H
+#define GUI_H
+
+#include <QObject>
+
+#include "../lib/src/loader.h"
+
+#ifndef NO_SYSTEMD
+#include "../lib/src/systemdcommunicator.h"
+
+#define SYSTEMD_BOOL true
+#else
+#define SYSTEMD_BOOL false
+#endif
+
+class GUI : public QObject
+{
+    Q_OBJECT
+    Q_PROPERTY(Loader* loader READ loader CONSTANT)
+    
+#ifndef NO_SYSTEMD
+    Q_PROPERTY(SystemdCommunicator* systemdCom READ systemdCommunicator CONSTANT)
+#endif
+
+public:
+    
+    GUI(QObject *parent = Q_NULLPTR);
+    
+    Loader *loader() const { return m_loader; }
+    
+    Q_INVOKABLE bool hasSystemdCommunicator() const { return SYSTEMD_BOOL; }
+    
+#ifndef NO_SYSTEMD
+    SystemdCommunicator *systemdCommunicator() const { return m_com; }
+#endif
+
+    
+protected:
+    
+    Loader *const m_loader = new Loader(this);
+    
+#ifndef NO_SYSTEMD
+    SystemdCommunicator *const m_com = new SystemdCommunicator(this);
+#endif
+    
+};
+
+#endif // GUI_H

+ 27 - 17
fancontrol-gui/src/main.cpp

@@ -20,36 +20,46 @@
 #include <QApplication>
 #include <QQmlApplicationEngine>
 #include <QQmlContext>
-#include <QQuickView>
 #include <KDeclarative/KDeclarative>
 #include <KI18n/KLocalizedString>
+#include <KPackage/PackageLoader>
+#include <KAboutData>
 
-#include "../share/src/loader.h"
+#include "gui.h"
 
-#ifndef NO_SYSTEMD
-#include "../share/src/systemdcommunicator.h"
-#endif
 
 int main(int argc, char *argv[])
 {
     QApplication app(argc, argv);
+    
+    KLocalizedString::setApplicationDomain("kcm_fancontrol");
+    
+    KAboutData about("fancontrol_gui",
+                    i18n("Fancontrol-GUI"),
+                    "0.1",
+                    i18n("Graphical user interface for fancontrol"),
+                    KAboutLicense::KAboutLicense::GPL_V2,
+                    "Copyright (C) 2015 Malte Veerman",
+                    QString(),
+                    "http://github.com/maldela/fancontrol-gui",
+                    "http://github.com/maldela/fancontrol-gui/issues");
+    about.addAuthor(i18n("Malte Veerman"), i18n("Main Developer"), "maldela@halloarsch.de"); 
+    KAboutData::setApplicationData(about);
 
     QQmlApplicationEngine engine;
+    
     KDeclarative::KDeclarative decl;
     decl.setDeclarativeEngine(&engine);
     decl.setupBindings();
-    KLocalizedString::setApplicationDomain("fancontrol-gui");
-    Loader loader;
-    engine.rootContext()->setContextProperty("loader", &loader);
-#ifndef NO_SYSTEMD
-    SystemdCommunicator com;
-    engine.rootContext()->setContextProperty("systemdCom", &com);
-#endif
-    qmlRegisterType<Hwmon>();
-    qmlRegisterType<Fan>();
-    qmlRegisterType<PwmFan>();
-    qmlRegisterType<Temp>();
-    engine.load(QUrl(QStringLiteral("file:///usr/share/fancontrol-gui/qml/fancontrol-gui.qml")));
+        
+    GUI gui;
+    engine.rootContext()->setContextProperty("gui", &gui);
+    
+    KPackage::Package package = KPackage::PackageLoader::self()->loadPackage("KPackage/GenericQML");
+    package.setDefaultPackageRoot("kpackage/kcms");
+    package.setPath("kcm_fancontrol");
+
+    engine.load(QUrl::fromLocalFile(package.path() + "/contents/ui/Application.qml"));
 
     return app.exec();
 }

+ 7 - 9
helper/CMakeLists.txt

@@ -1,19 +1,17 @@
-kauth_install_helper_files(fancontrol-gui-helper fancontrol.gui.helper root)
-kauth_install_actions(fancontrol.gui.helper fancontrol-gui.actions)
-
 set(LIBRARIES KF5::Auth)
 
-if(NO_SYSTEMD)
-
-    add_definitions(-DNO_SYSTEMD)
-
-else(NO_SYSTEMD)
+if(NOT NO_SYSTEMD)
 
     set(LIBRARIES ${LIBRARIES}
                   Qt5::DBus)
 
-endif(NO_SYSTEMD)
+endif(NOT NO_SYSTEMD)
+
+find_package(KF5Auth REQUIRED)
 
 add_executable(fancontrol-gui-helper src/helper.cpp)
 target_link_libraries(fancontrol-gui-helper ${LIBRARIES})
 install(TARGETS fancontrol-gui-helper DESTINATION ${KAUTH_HELPER_INSTALL_DIR})
+
+kauth_install_helper_files(fancontrol-gui-helper fancontrol.gui.helper root)
+kauth_install_actions(fancontrol.gui.helper fancontrol_gui.actions)

+ 9 - 0
helper/fancontrol_gui.actions

@@ -0,0 +1,9 @@
+[Domain]
+Name=Fancontrol-GUI
+URL=http://github.com/maldela/fancontrol-gui
+
+[fancontrol.gui.helper.action]
+Name=Action with su rights
+Description=This action requires superuser rights.
+Policy=auth_admin
+Persistence=session

+ 1 - 0
helper/src/helper.h

@@ -28,4 +28,5 @@ class Helper : public QObject
     public Q_SLOTS:
 
         ActionReply action(const QVariantMap &args);
+        ActionReply save(QVariantMap args) { args["action"] = "write"; return action(args); }
 };

+ 46 - 0
icon.svg

@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!-- Generator: Adobe Illustrator 18.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="0 0 32 32" style="enable-background:new 0 0 32 32;" xml:space="preserve">
+<g>
+	<path style="fill:#010002;" d="M24,20c6,0,8-2,8-2c0-4-2-6-2-6c-2.752,1.835-5.918,2.822-8.547,3.36
+		c0.97-0.464,2-1.157,3.031-2.188c4.244-4.243,4.244-7.071,4.244-7.071c-2.83-2.828-5.658-2.828-5.658-2.828
+		c-0.648,3.243-2.188,6.18-3.668,8.418C19.762,10.676,20,9.458,20,8c0-6-2-8-2-8c-4,0-6,2-6,2c1.835,2.753,2.823,5.919,3.36,8.548
+		c-0.464-0.971-1.157-2.001-2.189-3.034c-4.242-4.242-7.07-4.242-7.07-4.242C3.272,6.101,3.272,8.929,3.272,8.929
+		c3.244,0.649,6.18,2.189,8.419,3.668C10.677,12.239,9.458,12,8,12c-6,0-8,2-8,2c0,4,2,6,2,6c2.753-1.835,5.919-2.822,8.548-3.359
+		c-0.971,0.465-2.001,1.156-3.034,2.188c-4.243,4.241-4.243,7.069-4.243,7.069c2.829,2.828,5.657,2.828,5.657,2.828
+		c0.649-3.242,2.189-6.181,3.668-8.42C12.239,21.322,12,22.541,12,24c0,6,2,8,2,8c4,0,6-2,6-2c-1.835-2.752-2.822-5.918-3.359-8.547
+		c0.463,0.971,1.156,2.001,2.188,3.031c4.242,4.242,7.07,4.242,7.07,4.242c2.83-2.828,2.83-5.656,2.83-5.656
+		c-3.246-0.647-6.184-2.188-8.422-3.668C21.32,19.761,22.541,20,24,20z"/>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+</svg>

+ 14 - 0
kcm/CMakeLists.txt

@@ -0,0 +1,14 @@
+set(LIBRARIES KF5::QuickAddons
+              KF5::I18n
+              fancontrol_gui_lib)
+              
+find_package(KF5Declarative REQUIRED)
+
+add_library(kcm_fancontrol MODULE src/fancontrolkcm.cpp)
+                                  
+target_link_libraries(kcm_fancontrol ${LIBRARIES})
+
+install(TARGETS kcm_fancontrol DESTINATION "${LIB_INSTALL_DIR}/qt/plugins/kcms")
+install(FILES kcm_fancontrol.desktop DESTINATION ${SERVICES_INSTALL_DIR})
+
+kcoreaddons_desktop_to_json(kcm_fancontrol "kcm_fancontrol.desktop")

+ 14 - 0
kcm/kcm_fancontrol.desktop

@@ -0,0 +1,14 @@
+[Desktop Entry]
+Exec=kcmshell5 kcm_fancontrol
+Icon=fancontrol-gui
+Type=Service
+ 
+X-KDE-ServiceTypes=KCModule
+X-KDE-Library=kcm_fancontrol
+X-KDE-ParentApp=kcontrol
+X-KDE-Keywords=Fans,PWM,Temperature
+X-KDE-System-Settings-Parent-Category=hardware
+ 
+Name=Fancontrol
+Comment=Control PWM-fans
+Categories=Qt;KDE;X-KDE-settings-hardware;

+ 86 - 0
kcm/src/fancontrolkcm.cpp

@@ -0,0 +1,86 @@
+/*
+ * <one line to give the library's name and an idea of what it does.>
+ * Copyright 2015  <copyright holder> <email>
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License or (at your option) version 3 or any later version
+ * accepted by the membership of KDE e.V. (or its successor approved
+ * by the membership of KDE e.V.), which shall act as a proxy
+ * defined in Section 14 of version 3 of the license.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ * 
+ */
+
+#include "fancontrolkcm.h"
+
+#include <QtQml>
+#include <KAboutData>
+#include <KLocalizedString>
+#include <KPluginFactory>
+
+#include "../../lib/src/hwmon.h"
+#include "../../lib/src/sensors.h"
+
+
+K_PLUGIN_FACTORY_WITH_JSON(FancontrolKCMFactory, "kcm_fancontrol.json", registerPlugin<FancontrolKCM>();)
+
+FancontrolKCM::FancontrolKCM(QObject *parent, const QVariantList& args) : ConfigModule(parent, args)
+{
+    KLocalizedString::setApplicationDomain("fancontrol-gui");
+
+    KAboutData *about = new KAboutData("kcm_fancontrol",
+                                       i18n("Fancontrol-KCM"),
+                                       "0.1",
+                                       i18n("KDE Fancontrol Module"),
+                                       KAboutLicense::KAboutLicense::GPL_V2,
+                                       "Copyright (C) 2015 Malte Veerman",
+                                       QString(),
+                                       "http://github.com/maldela/fancontrol-gui",
+                                       "http://github.com/maldela/fancontrol-gui/issues");
+    about->addAuthor(i18n("Malte Veerman"), i18n("Main Developer"), "maldela@halloarsch.de"); 
+    setAboutData(about);
+    
+    setButtons(FancontrolKCM::Apply);
+    setAuthActionName("fancontrol.gui.helper.action");
+    
+    connect(m_loader, SIGNAL(configFileChanged()), this, SLOT(setNeedsSave()));
+    
+    qmlRegisterType<Loader>();
+    qmlRegisterType<Hwmon>();
+    qmlRegisterType<Fan>();
+    qmlRegisterType<PwmFan>();
+    qmlRegisterType<Temp>();
+    
+#ifndef NO_SYSTEMD
+    qmlRegisterType<SystemdCommunicator>();
+#endif
+}
+
+void FancontrolKCM::save()
+{
+    qDebug() << "saving...";
+    setNeedsSave(false);
+    m_loader->save();
+    
+#ifndef NO_SYSTEMD
+    m_communicator->restartService();
+#endif
+}
+
+void FancontrolKCM::load()
+{
+    setNeedsSave(false);
+    m_loader->load(QUrl::fromLocalFile("/etc/fancontrol"));
+}
+
+
+#include "fancontrolkcm.moc"

+ 80 - 0
kcm/src/fancontrolkcm.h

@@ -0,0 +1,80 @@
+/*
+ * <one line to give the library's name and an idea of what it does.>
+ * Copyright 2015  <copyright holder> <email>
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License or (at your option) version 3 or any later version
+ * accepted by the membership of KDE e.V. (or its successor approved
+ * by the membership of KDE e.V.), which shall act as a proxy
+ * defined in Section 14 of version 3 of the license.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ * 
+ */
+
+#ifndef FANCONTROLKCM_H
+#define FANCONTROLKCM_H
+
+#include <KQuickAddons/ConfigModule>
+
+#include "../../lib/src/loader.h"
+
+#ifndef NO_SYSTEMD
+#include "../../lib/src/systemdcommunicator.h"
+
+#define SYSTEMD_BOOL true
+#else
+#define SYSTEMD_BOOL false
+#endif
+
+using namespace KQuickAddons;
+
+class FancontrolKCM : public ConfigModule
+{
+    Q_OBJECT
+    Q_PROPERTY(Loader* loader READ loader CONSTANT)
+    
+#ifndef NO_SYSTEMD
+    Q_PROPERTY(SystemdCommunicator* systemdCom READ systemdCommunicator CONSTANT)
+#endif
+    
+public:
+    
+    explicit FancontrolKCM(QObject *parent, const QVariantList &args);
+    
+    Loader *loader() const { return m_loader; }
+        
+    Q_INVOKABLE bool hasSystemdCommunicator() const { return SYSTEMD_BOOL; }
+    
+#ifndef NO_SYSTEMD
+    SystemdCommunicator *systemdCommunicator() const { return m_communicator; }
+#endif
+        
+        
+public slots:
+    
+    void load() Q_DECL_OVERRIDE;
+    void save() Q_DECL_OVERRIDE;
+//     void defaults() { load(); }
+    
+    void setNeedsSave(bool needs = true) { ConfigModule::setNeedsSave(needs); qDebug() << "Needs save: " << needs; }
+    
+    
+protected:
+    
+    Loader *const m_loader = new Loader(this);
+    
+#ifndef NO_SYSTEMD
+    SystemdCommunicator *const m_communicator = new SystemdCommunicator(this);
+#endif
+};
+
+#endif // FANCONTROLKCM_H

+ 35 - 0
lib/CMakeLists.txt

@@ -0,0 +1,35 @@
+set(LIB_SRCS src/hwmon.cpp
+	     src/loader.cpp
+	     src/sensors.cpp)
+                        
+set(LIB_PRIVATE_LIBRARIES Qt5::Qml
+			  KF5::Auth
+			  KF5::ConfigCore)
+              
+if(NOT NO_SYSTEMD)
+
+    set(LIB_SRCS ${LIB_SRCS}
+                 src/systemdcommunicator.cpp)
+
+    set(LIB_HEADER ${LIB_HEADER}
+                   src/systemdcommunicator.h)
+
+    set(LIB_PUBLIC_LIBRARIES Qt5::DBus)
+
+endif(NOT NO_SYSTEMD)
+
+find_package(Qt5Qml REQUIRED)
+find_package(KF5 COMPONENTS Auth Config REQUIRED)
+
+include_directories(${Qt5Qml_INCLUDE_DIRS})
+
+add_library(fancontrol_gui_lib SHARED ${LIB_SRCS})
+target_link_libraries(fancontrol_gui_lib PRIVATE ${LIB_PRIVATE_LIBRARIES} PUBLIC ${LIB_PUBLIC_LIBRARIES})
+
+generate_export_header(fancontrol_gui_lib)
+
+if(INSTALL_SHARED)
+
+    install(TARGETS fancontrol_gui_lib LIBRARY DESTINATION "${CMAKE_INSTALL_PREFIX}/lib")
+    
+endif(INSTALL_SHARED)

+ 110 - 0
lib/src/hwmon.cpp

@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2015  Malte Veerman <maldela@halloarsch.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include "hwmon.h"
+#include <QDir>
+#include <QTextStream>
+#include <QtQml>
+#include <QDebug>
+
+Hwmon::Hwmon(const QString &path, Loader *parent) : QObject(parent)
+{
+    m_parent = parent;
+    m_path = path;
+    m_index = path.split('/').last().remove("hwmon").toInt();
+    QFile nameFile(path + "/name");
+    if (nameFile.open(QFile::ReadOnly))
+        m_name = QTextStream(&nameFile).readLine();
+    else
+        m_name = path.split('/').last();
+
+    QDir dir(m_path);
+    QStringList entrys = dir.entryList(QDir::Files | QDir::NoDotAndDotDot);
+    foreach (QString entry, entrys)
+    {
+        QString str = entry;
+        int index = str.remove(QRegExp("\\D+")).toInt();
+        if (entry.contains("fan") && entry.contains("input"))
+        {
+            if (QFile::exists(m_path + "/pwm" + QString::number(index)))
+            {
+                PwmFan *newPwmFan = new PwmFan(this, index);
+                connect(this, SIGNAL(sensorsUpdateNeeded()), newPwmFan, SLOT(update()));
+                m_pwmFans << newPwmFan;
+                emit pwmFansChanged();
+                m_fans << qobject_cast<Fan *>(newPwmFan);
+                emit fansChanged();
+            }
+            else
+            {
+                Fan *newFan = new Fan(this, index);
+                connect(this, SIGNAL(sensorsUpdateNeeded()), newFan, SLOT(update()));
+                m_fans << newFan;
+                emit fansChanged();
+            }
+        }
+
+        if (entry.contains("temp") && entry.contains("input"))
+        {
+            Temp *newTemp = new Temp(this, index);
+            connect(this, SIGNAL(sensorsUpdateNeeded()), newTemp, SLOT(update()));
+            m_temps << newTemp;
+            emit tempsChanged();
+        }
+    }
+//    qDebug() << "New Hwmon" << m_temps.size() << m_pwmFans.size();
+}
+
+QList<QObject *> Hwmon::fans() const
+{
+    QList<QObject *> list;
+    foreach (Fan *fan, m_fans)
+    {
+        list << qobject_cast<QObject *>(fan);
+    }
+    return list;
+}
+
+QList<QObject *> Hwmon::pwmFans() const
+{
+    QList<QObject *> list;
+    foreach (PwmFan *pwmFan, m_pwmFans)
+    {
+        list << qobject_cast<QObject *>(pwmFan);
+    }
+    return list;
+}
+
+QList<QObject *> Hwmon::temps() const
+{
+    QList<QObject *> list;
+    foreach (Temp *temp, m_temps)
+    {
+        list << qobject_cast<QObject *>(temp);
+    }
+    return list;
+}
+
+void Hwmon::testFans()
+{
+    for (int i=0; i<m_pwmFans.size(); i++)
+    {
+        m_pwmFans.at(i)->test();
+    }
+}

+ 87 - 0
lib/src/hwmon.h

@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2015  Malte Veerman <maldela@halloarsch.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#ifndef HWMON_H
+#define HWMON_H
+
+#include <QObject>
+
+#include "sensors.h"
+#include "loader.h"
+#include "fancontrol_gui_lib_export.h"
+
+class Fan;
+class PwmFan;
+class Temp;
+class Loader;
+
+class FANCONTROL_GUI_LIB_EXPORT Hwmon : public QObject
+{
+    Q_OBJECT
+    Q_PROPERTY(QString name READ name CONSTANT)
+    Q_PROPERTY(QString path READ path CONSTANT)
+    Q_PROPERTY(int index READ index CONSTANT)
+    Q_PROPERTY(QList<QObject *> fans READ fans NOTIFY fansChanged)
+    Q_PROPERTY(QList<QObject *> pwmFans READ pwmFans NOTIFY pwmFansChanged)
+    Q_PROPERTY(QList<QObject *> temps READ temps NOTIFY tempsChanged)
+
+
+public:
+
+    explicit Hwmon(const QString &, Loader *parent);
+
+    QString name() const { return m_name; }
+    QString path() const { return m_path; }
+    int index() const { return m_index; }
+    QList<QObject *> fans() const;
+    QList<QObject *> pwmFans() const;
+    QList<QObject *> temps() const;
+    Fan * fan(int i) const { return m_fans.value(i, Q_NULLPTR); }
+    PwmFan * pwmFan(int i) const { return m_pwmFans.value(i, Q_NULLPTR); }
+    Temp * temp(int i) const { return m_temps.value(i, Q_NULLPTR); }
+    Q_INVOKABLE void testFans();
+
+
+public slots:
+
+    void updateConfig() { emit configUpdateNeeded(); }
+    void updateSensors() { emit sensorsUpdateNeeded(); }
+
+
+signals:
+
+    void fansChanged();
+    void pwmFansChanged();
+    void tempsChanged();
+    void configUpdateNeeded();
+    void sensorsUpdateNeeded();
+
+
+protected:
+
+    Loader *m_parent;
+    QString m_name;
+    QString m_path;
+    int m_index;
+    QList<Fan *> m_fans;
+    QList<PwmFan *> m_pwmFans;
+    QList<Temp *> m_temps;
+};
+
+#endif // HWMON_H

+ 451 - 0
lib/src/loader.cpp

@@ -0,0 +1,451 @@
+/*
+ * Copyright (C) 2015  Malte Veerman <maldela@halloarsch.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include "loader.h"
+
+#include <QFile>
+#include <QDir>
+#include <QTextStream>
+#include <QDebug>
+
+#include <KF5/KAuth/kauthexecutejob.h>
+
+using namespace KAuth;
+
+#define HWMON_PATH "/sys/class/hwmon"
+
+Loader::Loader(QObject *parent) : QObject(parent)
+{
+    m_configUrl = QUrl::fromLocalFile("/etc/fancontrol");
+    m_interval = 10;
+    m_error = "Success";
+    
+    parseHwmons();
+    
+    m_timer.setSingleShot(false);
+    m_timer.start(1);
+    
+    connect(&m_timer, SIGNAL(timeout()), this, SLOT(updateSensors()));
+}
+
+void Loader::parseHwmons()
+{
+    foreach (Hwmon *hwmon, m_hwmons)
+    {
+        hwmon->deleteLater();
+    }
+    m_hwmons.clear();
+
+    QDir hwmonDir(HWMON_PATH);
+    QStringList list;
+    if (hwmonDir.isReadable())
+    {
+        list = hwmonDir.entryList(QDir::AllEntries | QDir::NoDotAndDotDot);
+    }
+    else
+    {
+        qDebug() << HWMON_PATH << " is not readable!";
+        return;
+    }
+
+    foreach (QString hwmonPath, list)
+    {
+        Hwmon *hwmon = new Hwmon(QFile::symLinkTarget(hwmonDir.absoluteFilePath(hwmonPath)), this);
+        connect(hwmon, SIGNAL(configUpdateNeeded()), this, SLOT(createConfigFile()));
+        connect(this, SIGNAL(sensorsUpdateNeeded()), hwmon, SLOT(updateSensors()));
+        m_hwmons << hwmon;
+    }
+    emit hwmonsChanged();
+}
+
+void Loader::load(const QUrl &url)
+{
+    QString fileName;
+    if (url.isEmpty())
+    {
+        qDebug() << "Given empty url. Fallback to " << m_configUrl;
+        fileName = m_configUrl.toLocalFile();
+    }
+    else
+        fileName = url.toLocalFile();
+    
+    QFile file(fileName);
+    QTextStream stream;
+    QString string;
+    QStringList lines;
+
+    if (file.open(QFile::ReadOnly | QFile::Text))
+    {
+        stream.setDevice(&file);
+        m_error = "Success";
+        emit errorChanged();
+    }
+    else if (file.exists())
+    {
+        Action action("fancontrol.gui.helper.action");
+        action.setHelperId("fancontrol.gui.helper");
+        QVariantMap map;
+        map["action"] = "read";
+        map["filename"] = fileName;
+        action.setArguments(map);
+        ExecuteJob *reply = action.execute();
+        if (!reply->exec())
+        {
+            m_error = reply->errorString();
+            emit errorChanged();
+            return;
+        }
+        else
+        {
+            m_error = "Success";
+            emit errorChanged();
+            string = reply->data()["content"].toString();
+            stream.setString(&string);
+        }
+    }
+    m_configFile = stream.readAll();
+    emit configFileChanged();
+    m_configUrl = url;
+    emit configUrlChanged();
+
+    stream.seek(0);
+
+    foreach (Hwmon *hwmon, m_hwmons)
+    {
+        foreach (QObject *pwmFan, hwmon->pwmFans())
+        {
+            qobject_cast<PwmFan *>(pwmFan)->reset();
+        }
+    }
+    
+    do
+    {
+        QString line(stream.readLine());
+        if (line.startsWith('#')) continue;
+        int offset = line.indexOf('#');
+        if (offset != -1) line.truncate(offset-1);
+        line = line.simplified();
+        lines << line;
+    }
+    while(!stream.atEnd());
+
+    foreach (QString line, lines)
+    {
+        if (line.startsWith("INTERVAL="))
+        {
+            setInterval(line.remove("INTERVAL=").toInt());
+        }
+        else if (line.startsWith("FCTEMPS="))
+        {
+            QStringList fctemps = line.split(' ');
+            if (!fctemps.isEmpty())
+                fctemps.first().remove("FCTEMPS=");
+            foreach (QString fctemp, fctemps)
+            {
+                QString pwm = fctemp.split('=').at(0);
+                QString temp = fctemp.split('=').at(1);
+                int pwmSensorIndex = getSensorNumber(pwm);
+                int tempSensorIndex = getSensorNumber(temp);
+                Hwmon *pwmHwmon = m_hwmons.value(getHwmonNumber(pwm), Q_NULLPTR);
+
+                if (pwmHwmon)
+                {
+                    Hwmon *tempHwmon = m_hwmons.value(getHwmonNumber(temp), Q_NULLPTR);
+                    PwmFan *pwmPointer = pwmHwmon->pwmFan(pwmSensorIndex);
+                    if (tempHwmon)
+                    {
+                        Temp *tempPointer = tempHwmon->temp(tempSensorIndex);
+
+                        if (pwmPointer)
+                        {
+                            pwmPointer->setTemp(tempPointer);
+                            pwmPointer->setMinPwm(0);
+                        }
+                    }
+                    else if (pwmPointer)
+                        pwmPointer->setTemp(Q_NULLPTR);
+                }
+            }
+        }
+        else if (line.startsWith("MINTEMP="))
+        {
+            QStringList mintemps = line.split(' ');
+            if (!mintemps.isEmpty())
+                mintemps.first().remove("MINTEMP=");
+            foreach (QString mintemp, mintemps)
+            {
+                QString pwm = mintemp.split('=').at(0);
+                int value = mintemp.split('=').at(1).toInt();
+                int pwmHwmon = getHwmonNumber(pwm);
+                int pwmSensor = getSensorNumber(pwm);
+                PwmFan *pwmPointer = m_hwmons.value(pwmHwmon, Q_NULLPTR)->pwmFan(pwmSensor);
+                if (pwmPointer)
+                    pwmPointer->setMinTemp(value);
+            }
+        }
+        else if (line.startsWith("MAXTEMP="))
+        {
+            QStringList maxtemps = line.split(' ');
+            if (!maxtemps.isEmpty())
+                maxtemps.first().remove("MAXTEMP=");
+            foreach (QString maxtemp, maxtemps)
+            {
+                QString pwm = maxtemp.split('=').at(0);
+                int value = maxtemp.split('=').at(1).toInt();
+                int pwmHwmon = getHwmonNumber(pwm);
+                int pwmSensor = getSensorNumber(pwm);
+                PwmFan *pwmPointer = m_hwmons.value(pwmHwmon, Q_NULLPTR)->pwmFan(pwmSensor);
+                if (pwmPointer)
+                    pwmPointer->setMaxTemp(value);
+            }
+        }
+        else if (line.startsWith("MINSTART="))
+        {
+            QStringList minstarts = line.split(' ');
+            if (!minstarts.isEmpty())
+                minstarts.first().remove("MINSTART=");
+            foreach (QString minstart, minstarts)
+            {
+                QString pwm = minstart.split('=').at(0);
+                int value = minstart.split('=').at(1).toInt();
+                int pwmHwmon = getHwmonNumber(pwm);
+                int pwmSensor = getSensorNumber(pwm);
+                PwmFan *pwmPointer = m_hwmons.value(pwmHwmon, Q_NULLPTR)->pwmFan(pwmSensor);
+                if (pwmPointer)
+                    pwmPointer->setMinStart(value);
+            }
+        }
+        else if (line.startsWith("MINSTOP="))
+        {
+            QStringList minstops = line.split(' ');
+            if (!minstops.isEmpty())
+                minstops.first().remove("MINSTOP=");
+            foreach (QString minstop, minstops)
+            {
+                QString pwm = minstop.split('=').at(0);
+                int value = minstop.split('=').at(1).toInt();
+                int pwmHwmon = getHwmonNumber(pwm);
+                int pwmSensor = getSensorNumber(pwm);
+                PwmFan *pwmPointer = m_hwmons.value(pwmHwmon, Q_NULLPTR)->pwmFan(pwmSensor);
+                if (pwmPointer)
+                    pwmPointer->setMinStop(value);
+            }
+        }
+        else if (line.startsWith("MINPWM="))
+        {
+            QStringList minpwms = line.split(' ');
+            if (!minpwms.isEmpty())
+                minpwms.first().remove("MINPWM=");
+            foreach (QString minpwm, minpwms)
+            {
+                QString pwm = minpwm.split('=').at(0);
+                int value = minpwm.split('=').at(1).toInt();
+                int pwmHwmon = getHwmonNumber(pwm);
+                int pwmSensor = getSensorNumber(pwm);
+                PwmFan *pwmPointer = m_hwmons.value(pwmHwmon, Q_NULLPTR)->pwmFan(pwmSensor);
+                if (pwmPointer)
+                    pwmPointer->setMinPwm(value);
+            }
+        }
+        else if (line.startsWith("MAXPWM="))
+        {
+            QStringList maxpwms = line.split(' ');
+            if (!maxpwms.isEmpty())
+                maxpwms.first().remove("MAXPWM=");
+            foreach (QString maxpwm, maxpwms)
+            {
+                QString pwm = maxpwm.split('=').at(0);
+                int value = maxpwm.split('=').at(1).toInt();
+                int pwmHwmon = getHwmonNumber(pwm);
+                int pwmSensor = getSensorNumber(pwm);
+                PwmFan *pwmPointer = m_hwmons.value(pwmHwmon, Q_NULLPTR)->pwmFan(pwmSensor);
+                if (pwmPointer)
+                    pwmPointer->setMaxPwm(value);
+            }
+        }
+    }
+}
+
+void Loader::save(const QUrl &url)
+{  
+    QString fileName = url.isEmpty() ? m_configUrl.toLocalFile() : url.toLocalFile();
+    QFile file(fileName);
+    
+    if (file.open(QFile::WriteOnly | QFile::Text))
+    {
+        QTextStream stream(&file);
+        stream << m_configFile;
+        qDebug() << m_configFile;
+    }
+    else
+    {
+        Action action("fancontrol.gui.helper.action");
+        action.setHelperId("fancontrol.gui.helper");
+
+        QVariantMap map;
+        map["action"] = "write";
+        map["filename"] = fileName;
+        map["content"] = m_configFile;
+
+        action.setArguments(map);
+        ExecuteJob *reply = action.execute();
+
+        if (!reply->exec())
+        {
+            m_error = reply->errorString();
+            emit errorChanged();
+        }
+    }
+}
+
+void Loader::createConfigFile()
+{
+    QList<Hwmon *> usedHwmons;
+    QList<PwmFan *> usedFans;
+    foreach (Hwmon *hwmon, m_hwmons)
+    {
+        if (hwmon->pwmFans().size() > 0)
+            usedHwmons << hwmon;
+        foreach (QObject *fan, hwmon->pwmFans())
+        {
+            PwmFan *pwmFan = qobject_cast<PwmFan *>(fan);
+            if (pwmFan->hasTemp() && pwmFan->temp())
+            {
+                usedFans << pwmFan;
+                if (!usedHwmons.contains(pwmFan->temp()->parent()))
+                    usedHwmons << pwmFan->temp()->parent();
+            }
+        }
+    }
+    
+    m_configFile = "# This file was created by Fancontrol-GUI \n";
+
+    m_configFile += "INTERVAL=" + QString::number(m_interval) + "\n";
+
+    m_configFile += "DEVPATH=";
+    foreach (Hwmon *hwmon, usedHwmons)
+    {
+        QString sanitizedPath = hwmon->path();
+        sanitizedPath.remove(QRegExp("^/sys/"));
+        sanitizedPath.remove(QRegExp("/hwmon/hwmon\\d\\s*$"));
+        m_configFile += "hwmon" + QString::number(hwmon->index()) + "=" + sanitizedPath + " ";
+    }
+    m_configFile += "\n";
+
+    m_configFile += "DEVNAME=";
+    foreach (Hwmon *hwmon, usedHwmons)
+    {
+        m_configFile += "hwmon" + QString::number(hwmon->index()) + "=" + hwmon->name().split('.').first() + " ";
+    }
+    m_configFile += "\n";
+
+    m_configFile += "FCTEMPS=";
+    foreach (PwmFan *pwmFan, usedFans)
+    {
+        m_configFile += "hwmon" + QString::number(pwmFan->parent()->index()) + "/";
+        m_configFile += "pwm" + QString::number(pwmFan->index()) + "=";
+        m_configFile += "hwmon" + QString::number(pwmFan->temp()->parent()->index()) + "/";
+        m_configFile += "temp" + QString::number(pwmFan->temp()->index()) + "_input ";
+    }
+    m_configFile += "\n";
+
+    m_configFile += "FCFANS=";
+    foreach (PwmFan *pwmFan, usedFans)
+    {
+        m_configFile += "hwmon" + QString::number(pwmFan->parent()->index()) + "/";
+        m_configFile += "pwm" + QString::number(pwmFan->index()) + "=";
+        m_configFile += "hwmon" + QString::number(pwmFan->parent()->index()) + "/";
+        m_configFile += "fan" + QString::number(pwmFan->index()) + "_input ";
+    }
+    m_configFile += "\n";
+
+    m_configFile += "MINTEMP=";
+    foreach (PwmFan *pwmFan, usedFans)
+    {
+        m_configFile += "hwmon" + QString::number(pwmFan->parent()->index()) + "/";
+        m_configFile += "pwm" + QString::number(pwmFan->index()) + "=";
+        m_configFile += QString::number(pwmFan->minTemp()) + " ";
+    }
+    m_configFile += "\n";
+
+    m_configFile += "MAXTEMP=";
+    foreach (PwmFan *pwmFan, usedFans)
+    {
+        m_configFile += "hwmon" + QString::number(pwmFan->parent()->index()) + "/";
+        m_configFile += "pwm" + QString::number(pwmFan->index()) + "=";
+        m_configFile += QString::number(pwmFan->maxTemp()) + " ";
+    }
+    m_configFile += "\n";
+
+    m_configFile += "MINSTART=";
+    foreach (PwmFan *pwmFan, usedFans)
+    {
+        m_configFile += "hwmon" + QString::number(pwmFan->parent()->index()) + "/";
+        m_configFile += "pwm" + QString::number(pwmFan->index()) + "=";
+        m_configFile += QString::number(pwmFan->minStart()) + " ";
+    }
+    m_configFile += "\n";
+
+    m_configFile += "MINSTOP=";
+    foreach (PwmFan *pwmFan, usedFans)
+    {
+        m_configFile += "hwmon" + QString::number(pwmFan->parent()->index()) + "/";
+        m_configFile += "pwm" + QString::number(pwmFan->index()) + "=";
+        m_configFile += QString::number(pwmFan->minStop()) + " ";
+    }
+    m_configFile += "\n";
+
+    m_configFile += "MINPWM=";
+    foreach (PwmFan *pwmFan, usedFans)
+    {
+        m_configFile += "hwmon" + QString::number(pwmFan->parent()->index()) + "/";
+        m_configFile += "pwm" + QString::number(pwmFan->index()) + "=";
+        m_configFile += QString::number(pwmFan->minPwm()) + " ";
+    }
+    m_configFile += "\n";
+
+    m_configFile += "MAXPWM=";
+    foreach (PwmFan *pwmFan, usedFans)
+    {
+        m_configFile += "hwmon" + QString::number(pwmFan->parent()->index()) + "/";
+        m_configFile += "pwm" + QString::number(pwmFan->index()) + "=";
+        m_configFile += QString::number(pwmFan->maxPwm()) + " ";
+    }
+    m_configFile += "\n";
+
+    emit configFileChanged();
+}
+
+void Loader::testFans()
+{
+    for (int i=0; i<m_hwmons.size(); i++)
+    {
+        m_hwmons.at(i)->testFans();
+    }
+}
+
+QList<QObject *> Loader::hwmons() const
+{
+    QList<QObject *> list;
+    foreach (Hwmon *hwmon, m_hwmons)
+    {
+        list << qobject_cast<QObject *>(hwmon);
+    }
+    return list;
+}

+ 92 - 0
lib/src/loader.h

@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2015  Malte Veerman <maldela@halloarsch.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#ifndef LOADER_H
+#define LOADER_H
+
+#include <QObject>
+#include <QUrl>
+#include <QTimer>
+
+#include "hwmon.h"
+#include "fancontrol_gui_lib_export.h"
+
+class Hwmon;
+
+class FANCONTROL_GUI_LIB_EXPORT Loader : public QObject
+{
+    Q_OBJECT
+    Q_PROPERTY(QUrl configUrl READ configUrl NOTIFY configUrlChanged)
+    Q_PROPERTY(QString configFile READ configFile NOTIFY configFileChanged)
+    Q_PROPERTY(QList<QObject *> hwmons READ hwmons NOTIFY hwmonsChanged)
+    Q_PROPERTY(int interval READ interval WRITE setInterval NOTIFY intervalChanged)
+    Q_PROPERTY(QString error READ error NOTIFY errorChanged)
+    
+
+public:
+
+    explicit Loader(QObject *parent = 0);
+    
+    Q_INVOKABLE void parseHwmons();
+    Q_INVOKABLE void load(const QUrl & = QUrl());
+    Q_INVOKABLE void save(const QUrl & = QUrl());
+    Q_INVOKABLE void testFans();
+    QUrl configUrl() const { return m_configUrl; }
+    QString configFile() const { return m_configFile; }
+    QList<QObject *> hwmons() const;
+    int interval() { return m_interval; }
+    void setInterval(int interval) { if (interval != m_interval) { m_interval = interval; emit intervalChanged(m_interval*1000); createConfigFile(); } }
+    Hwmon * hwmon(int i) { return m_hwmons.value(i, Q_NULLPTR); }
+    QString error() const { return m_error; }
+    
+    static int getHwmonNumber(const QString &str) { return str.split('/').value(0).remove("hwmon").toInt(); }
+    static int getSensorNumber(const QString &str) { return str.split('/').value(1).remove(QRegExp("pwm|fan|temp|_input")).toInt() - 1; }
+
+public slots:
+
+    void updateSensors() { emit sensorsUpdateNeeded(); }
+
+
+protected slots:
+
+    void createConfigFile();
+
+
+protected:
+
+    bool m_parsed;
+    int m_interval;
+    QList<Hwmon *> m_hwmons;
+    QUrl m_configUrl;
+    QString m_configFile;
+    QString m_error;
+    QTimer m_timer;
+
+
+signals:
+
+    void configUrlChanged();
+    void configFileChanged();
+    void hwmonsChanged();
+    void intervalChanged(int);
+    void errorChanged();
+    void sensorsUpdateNeeded();
+};
+
+#endif // LOADER_H

+ 377 - 0
lib/src/sensors.cpp

@@ -0,0 +1,377 @@
+/*
+ * Copyright (C) 2015  Malte Veerman <maldela@halloarsch.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include "sensors.h"
+#include <QFile>
+#include <QDir>
+#include <QDebug>
+#include <KConfigGroup>
+#include <KSharedConfig>
+#include <KF5/KAuth/KAuthExecuteJob>
+
+Sensor::Sensor(Hwmon *parent, uint index) : QObject(parent),
+                                            m_parent(parent),
+                                            m_index(index)
+{
+}
+
+
+Fan::Fan(Hwmon *parent, uint index) : Sensor(parent, index)
+{
+    if (QDir(parent->path()).isReadable())
+    {
+        QFile *rpmFile = new QFile(parent->path() + "/fan" + QString::number(index) + "_input", this);
+
+        if (rpmFile->open(QFile::ReadOnly))
+        {
+            m_rpmStream.setDevice(rpmFile);
+            m_rpmStream >> m_rpm;
+        }
+        else
+        {
+            qDebug() << "Can't open rpmFile " << parent->path() + "/fan" + QString::number(index) + "_input";
+        }
+    }
+}
+
+QString Fan::name() const
+{
+    KConfigGroup names = KSharedConfig::openConfig("fancontrol-gui")->group("names");
+    KConfigGroup localNames = names.group(m_parent->name());
+    QString name = localNames.readEntry("fan" + QString::number(m_index), QString());
+    if (name.isEmpty())
+        return "fan" + QString::number(m_index);
+    return name;
+}
+
+void Fan::setName(const QString &name)
+{
+    KConfigGroup names = KSharedConfig::openConfig("fancontrol-gui")->group("names");
+    KConfigGroup localNames = names.group(m_parent->name());
+    if (name != localNames.readEntry("fan" + QString::number(m_index), QString())
+        && !name.isEmpty())
+    {
+        localNames.writeEntry("fan" + QString::number(m_index), name);
+        emit nameChanged();
+    }
+}
+
+void Fan::update()
+{
+    m_rpmStream.seek(0);
+    m_rpmStream >> m_rpm;
+    emit rpmChanged();
+}
+
+
+PwmFan::PwmFan(Hwmon *parent, uint index) : Fan(parent, index)
+{
+    m_temp = Q_NULLPTR;
+    m_hasTemp = false;
+    m_minTemp = 0;
+    m_maxTemp = 100;
+    m_minPwm = 255;
+    m_maxPwm = 255;
+    m_minStart = 255;
+    m_minStop = 255;
+
+    m_testTimer.setSingleShot(true);
+
+    m_testStatus = notTesting;
+    m_testing = false;
+
+    connect(this, SIGNAL(tempChanged()), parent, SLOT(updateConfig()));
+    connect(this, SIGNAL(hasTempChanged()), parent, SLOT(updateConfig()));
+    connect(this, SIGNAL(minTempChanged()), parent, SLOT(updateConfig()));
+    connect(this, SIGNAL(maxTempChanged()), parent, SLOT(updateConfig()));
+    connect(this, SIGNAL(minPwmChanged()), parent, SLOT(updateConfig()));
+    connect(this, SIGNAL(maxPwmChanged()), parent, SLOT(updateConfig()));
+    connect(this, SIGNAL(minStartChanged()), parent, SLOT(updateConfig()));
+    connect(this, SIGNAL(minStopChanged()), parent, SLOT(updateConfig()));
+    connect(&m_testTimer, SIGNAL(timeout()), this, SLOT(continueTesting()));
+
+    if (QDir(parent->path()).isReadable())
+    {
+        QFile *pwmFile = new QFile(parent->path() + "/pwm" + QString::number(index), this);
+        if (pwmFile->open(QFile::ReadWrite))
+        {
+            m_pwmStream.setDevice(pwmFile);
+            m_pwmStream >> m_pwm;
+        }
+        else if (pwmFile->open(QFile::ReadOnly))
+        {
+            m_pwmStream.setDevice(pwmFile);
+            m_pwmStream >> m_pwm;
+        }
+        else
+            qDebug() << "Can't open pwmFile " << pwmFile->fileName();
+
+        QFile *pwmModeFile = new QFile(parent->path() + "/pwm" + QString::number(index) + "_mode", this);
+        if (pwmModeFile->open(QFile::ReadWrite))
+        {
+            m_modeStream.setDevice(pwmModeFile);
+            m_modeStream >> m_pwmMode;
+        }
+        else if (pwmModeFile->open(QFile::ReadOnly))
+        {
+            m_modeStream.setDevice(pwmModeFile);
+            m_modeStream >> m_pwmMode;
+        }
+        else
+            qDebug() << "Can't open pwmModeFile " << pwmModeFile->fileName();
+    }
+}
+
+void PwmFan::update()
+{
+    Fan::update();
+
+    m_pwmStream.seek(0);
+    setPwm(m_pwmStream.readAll().toInt(), false);
+
+    m_modeStream.seek(0);
+    setPwmMode(m_modeStream.readAll().toInt(), false);
+}
+
+void PwmFan::setPwm(int pwm, bool write)
+{
+    if (m_pwm != pwm)
+    {
+        m_pwm = pwm;
+        emit pwmChanged();
+
+        if (write)
+        {
+            if (m_pwmStream.device()->isWritable())
+                m_pwmStream << pwm;
+            else
+            {
+                KAuth::Action action("fancontrol.gui.helper.action");
+                action.setHelperId("fancontrol.gui.helper");
+                QVariantMap map;
+                map["action"] = "write";
+                map["filename"] = qobject_cast<QFile *>(m_pwmStream.device())->fileName();
+                map["content"] = QString::number(pwm);
+                action.setArguments(map);
+                KAuth::ExecuteJob *reply = action.execute();
+
+                if (!reply->exec())
+                    qDebug() << reply->errorString() << reply->errorText();
+            }
+        }
+    }
+}
+
+void PwmFan::setPwmMode(int pwmMode, bool write)
+{
+    if (m_pwmMode != pwmMode)
+    {
+        m_pwmMode = pwmMode;
+        emit pwmModeChanged();
+
+        if (write)
+        {
+            if (m_modeStream.device()->isWritable())
+                m_modeStream << pwmMode;
+            else
+            {
+                KAuth::Action action("fancontrol.gui.helper.action");
+                action.setHelperId("fancontrol.gui.helper");
+                QVariantMap map;
+                map["action"] = "write";
+                map["filename"] = qobject_cast<QFile *>(m_modeStream.device())->fileName();
+                map["content"] = QString::number(pwmMode);
+                action.setArguments(map);
+                KAuth::ExecuteJob *reply = action.execute();
+
+                if (!reply->exec())
+                    qDebug() << reply->errorString() << reply->errorText();
+            }
+        }
+    }
+}
+
+void PwmFan::test()
+{
+    m_testing = true;
+    emit testingChanged();
+
+    m_testStatus = findingStop1;
+    setPwmMode(1);
+    setPwm(255);
+    m_testTimer.setInterval(2000);
+    m_testTimer.start();
+    qDebug() << "Start testing...";
+}
+
+void PwmFan::abortTest()
+{
+    setPwm(255);
+    m_testTimer.stop();
+
+    m_testing = false;
+    emit testingChanged();
+}
+
+void PwmFan::continueTest()
+{
+    update();
+    switch (m_testStatus)
+    {
+    case findingStop1:
+        if (m_rpm > 0)
+            setPwm(qMin(m_pwm * 0.9, m_pwm - 5.0));
+        else
+        {
+            m_testStatus = findingStart;
+            m_testTimer.setInterval(500);
+            qDebug() << "Start finding start value...";
+        }
+        m_testTimer.start();
+        break;
+
+    case findingStart:
+        if (m_rpm == 0)
+            setPwm(m_pwm + 2);
+        else
+        {
+            m_testStatus = findingStop2;
+            m_testTimer.setInterval(1000);
+            setMinStart(m_pwm);
+            qDebug() << "Start finding stop value...";
+        }
+        m_testTimer.start();
+        break;
+
+    case findingStop2:
+        if (m_rpm > 0)
+        {
+            setPwm(m_pwm - 1);
+            m_testTimer.start();
+        }
+        else
+        {
+            m_testStatus = notTesting;
+            m_testing = false;
+            emit testingChanged();
+            setMinStop(m_pwm + 5);
+            qDebug() << "Finished testing!";
+        }
+        break;
+
+    case notTesting:
+        m_testing = false;
+        emit testingChanged();
+        break;
+
+    default:
+        break;
+    }
+}
+
+void PwmFan::reset()
+{
+    setTemp(Q_NULLPTR);
+    setMinTemp(0);
+    setMaxTemp(100);
+    setMinPwm(255);
+    setMaxPwm(255);
+    setMinStart(255);
+    setMinStop(255);
+}
+
+bool PwmFan::active() const
+{
+    KConfigGroup active = KSharedConfig::openConfig("fancontrol-gui")->group("active");
+    KConfigGroup localActive = active.group(m_parent->name());
+    return localActive.readEntry("pwmfan" + QString::number(m_index), true);
+}
+
+void PwmFan::setActive(bool a)
+{
+    KConfigGroup active = KSharedConfig::openConfig("fancontrol-gui")->group("active");
+    KConfigGroup localActive = active.group(m_parent->name());
+    if (a != localActive.readEntry("pwmfan" + QString::number(m_index), true))
+    {
+        localActive.writeEntry("pwmfan" + QString::number(m_index), a);
+        emit activeChanged();
+    }
+}
+
+
+Temp::Temp(Hwmon *parent, uint index) : Sensor(parent, index)
+{
+    if (QDir(parent->path()).isReadable())
+    {
+        QFile *valueFile = new QFile(parent->path() + "/temp" + QString::number(index) + "_input", this);
+        QFile labelFile(parent->path() + "/temp" + QString::number(index) + "_label");
+
+        if (valueFile->open(QFile::ReadOnly))
+        {
+            m_valueStream.setDevice(valueFile);
+            m_valueStream >> m_value;
+            m_value /= 1000;
+        }
+        else
+            qDebug() << "Can't open valueFile " << parent->path() + "/temp" + QString::number(index) + "_input";
+
+        if (labelFile.open(QFile::ReadOnly))
+        {
+            m_label = QTextStream(&labelFile).readLine();
+        }
+        else
+        {
+            qDebug() << "Can't open labelFile " << parent->path() + "/temp" + QString::number(index) + "_label";
+        }
+    }
+}
+
+QString Temp::name() const
+{
+    KConfigGroup names = KSharedConfig::openConfig("fancontrol-gui")->group("names");
+    KConfigGroup localNames = names.group(m_parent->name());
+    QString name = localNames.readEntry("temp" + QString::number(m_index), QString());
+    if (name.isEmpty())
+    {
+        if (m_label.isEmpty())
+            return "temp" + QString::number(m_index);
+        return m_label;
+    }
+    return name;
+}
+
+void Temp::setName(const QString &name)
+{
+    KConfigGroup names = KSharedConfig::openConfig("fancontrol-gui")->group("names");
+    KConfigGroup localNames = names.group(m_parent->name());
+    if (name != localNames.readEntry("temp" + QString::number(m_index), QString())
+        && !name.isEmpty())
+    {
+        localNames.writeEntry(m_parent->name() + "temp" + QString::number(m_index), name);
+        emit nameChanged();
+    }
+}
+
+void Temp::update()
+{
+    m_valueStream.seek(0);
+    m_valueStream >> m_value;
+    m_value /= 1000;
+    emit valueChanged();
+}

+ 231 - 0
lib/src/sensors.h

@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2015  Malte Veerman <maldela@halloarsch.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#ifndef SENSORS_H
+#define SENSORS_H
+
+#include <QObject>
+#include <QTextStream>
+#include <QTimer>
+
+#include "hwmon.h"
+#include "fancontrol_gui_lib_export.h"
+
+class Hwmon;
+
+class Sensor : public QObject
+{
+    Q_OBJECT
+    Q_PROPERTY(uint index READ index CONSTANT)
+    Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
+    Q_PROPERTY(Hwmon * parent READ parent CONSTANT)
+
+public:
+
+    explicit Sensor(Hwmon *parent, uint index);
+
+    virtual QString name() const = 0;
+    virtual void setName(const QString &name) = 0;
+    Hwmon * parent() const { return m_parent; }
+    uint index() const { return m_index; }
+
+
+public slots:
+
+    virtual void update() = 0;
+
+
+signals:
+
+    void nameChanged();
+
+
+protected:
+
+    Hwmon *const m_parent;
+    const uint m_index;
+};
+
+
+class FANCONTROL_GUI_LIB_EXPORT Temp : public Sensor
+{
+    Q_OBJECT
+    Q_PROPERTY(QString label READ label NOTIFY labelChanged)
+    Q_PROPERTY(int value READ value NOTIFY valueChanged)
+
+public:
+
+    explicit Temp(Hwmon *parent, uint index);
+
+    QString label() const { return m_label; }
+    int value() const { return m_value; }
+    QString name() const;
+    void setName(const QString &name);
+
+
+public slots:
+
+    void update();
+
+
+signals:
+
+    void labelChanged();
+    void valueChanged();
+
+
+protected:
+
+    QString m_label;
+    int m_value;
+    QTextStream m_valueStream;
+};
+
+
+class FANCONTROL_GUI_LIB_EXPORT Fan : public Sensor
+{
+    Q_OBJECT
+    Q_PROPERTY(int rpm READ rpm NOTIFY rpmChanged)
+
+public:
+
+    explicit Fan(Hwmon *parent, uint index);
+
+    int rpm() const { return m_rpm; }
+    QString name() const;
+    void setName(const QString &name);
+
+    virtual int pwm() const { return 255; }
+    virtual void setPwm(int, bool) { }
+
+
+signals:
+
+    void rpmChanged();
+
+
+public slots:
+
+    void update();
+
+
+protected:
+
+    int m_rpm;
+    QTextStream m_rpmStream;
+};
+
+
+class FANCONTROL_GUI_LIB_EXPORT PwmFan : public Fan
+{
+    Q_OBJECT
+    Q_PROPERTY(int pwm READ pwm WRITE setPwm NOTIFY pwmChanged)
+    Q_PROPERTY(Temp * temp READ temp WRITE setTemp NOTIFY tempChanged)
+    Q_PROPERTY(bool hasTemp READ hasTemp WRITE setHasTemp NOTIFY hasTempChanged)
+    Q_PROPERTY(int minTemp READ minTemp WRITE setMinTemp NOTIFY minTempChanged)
+    Q_PROPERTY(int maxTemp READ maxTemp WRITE setMaxTemp NOTIFY maxTempChanged)
+    Q_PROPERTY(int minPwm READ minPwm WRITE setMinPwm NOTIFY minPwmChanged)
+    Q_PROPERTY(int maxPwm READ maxPwm WRITE setMaxPwm NOTIFY maxPwmChanged)
+    Q_PROPERTY(int minStart READ minStart WRITE setMinStart NOTIFY minStartChanged)
+    Q_PROPERTY(int minStop READ minStop WRITE setMinStop NOTIFY minStopChanged)
+    Q_PROPERTY(bool active READ active WRITE setActive NOTIFY activeChanged)
+    Q_PROPERTY(bool testing READ testing NOTIFY testingChanged)
+    Q_PROPERTY(int pwmMode READ pwmMode WRITE setPwmMode NOTIFY pwmModeChanged)
+
+public:
+
+    explicit PwmFan(Hwmon *parent, uint index);
+
+    int pwm() const { return m_pwm; }
+    Temp * temp() const { return m_temp; }
+    bool hasTemp() const { return m_hasTemp; }
+    int minTemp() const { return m_minTemp; }
+    int maxTemp() const { return m_maxTemp; }
+    int minPwm() const { return m_minPwm; }
+    int maxPwm() const { return m_maxPwm; }
+    int minStart() const { return m_minStart; }
+    int minStop() const { return m_minStop; }
+    int pwmMode() const { return m_pwmMode; }
+    bool active() const;
+    bool testing() const { return m_testing; }
+    void setPwm(int pwm, bool write = true);
+    void setTemp(Temp *temp) { setHasTemp(temp != Q_NULLPTR); if (temp != m_temp) { m_temp = temp; emit tempChanged(); } }
+    void setHasTemp(bool hasTemp) { if (hasTemp != m_hasTemp) { m_hasTemp = hasTemp; emit hasTempChanged(); } }
+    void setMinTemp(int minTemp) { if (minTemp != m_minTemp) { m_minTemp = minTemp; emit minTempChanged(); } }
+    void setMaxTemp(int maxTemp) { if (maxTemp != m_maxTemp) { m_maxTemp = maxTemp; emit maxTempChanged(); } }
+    void setMinPwm(int minPwm) { if (minPwm != m_minPwm) { m_minPwm = minPwm; emit minPwmChanged(); } }
+    void setMaxPwm(int maxPwm) { if (maxPwm != m_maxPwm) { m_maxPwm = maxPwm; emit maxPwmChanged(); } }
+    void setMinStart(int minStart) { if (minStart != m_minStart) { m_minStart = minStart; emit minStartChanged(); } }
+    void setMinStop(int minStop) { if (minStop != m_minStop) { m_minStop = minStop; emit minStopChanged(); } }
+    void setPwmMode(int pwmMode, bool write = true);
+    void setActive(bool active);
+    void reset();
+    Q_INVOKABLE void test();
+    Q_INVOKABLE void abortTest();
+
+
+signals:
+
+    void pwmChanged();
+    void tempChanged();
+    void hasTempChanged();
+    void minTempChanged();
+    void maxTempChanged();
+    void minPwmChanged();
+    void maxPwmChanged();
+    void minStartChanged();
+    void minStopChanged();
+    void activeChanged();
+    void testingChanged();
+    void pwmModeChanged();
+
+
+protected slots:
+
+    void update();
+    void continueTest();
+
+
+protected:
+
+    int m_pwm;
+    QTextStream m_pwmStream;
+    QTextStream m_modeStream;
+    QTimer m_testTimer;
+    Temp *m_temp;
+    bool m_hasTemp;
+    bool m_testing;
+    int m_minTemp;
+    int m_maxTemp;
+    int m_minPwm;
+    int m_maxPwm;
+    int m_minStart;
+    int m_minStop;
+    int m_pwmMode;
+
+    enum
+    {
+        findingStop1,
+        findingStop2,
+        findingStart,
+        notTesting
+    } m_testStatus;
+};
+
+#endif // SENSORS_H

+ 280 - 0
lib/src/systemdcommunicator.cpp

@@ -0,0 +1,280 @@
+/*
+ * Copyright (C) 2015  Malte Veerman <maldela@halloarsch.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include "systemdcommunicator.h"
+
+#include <KF5/KAuth/kauthexecutejob.h>
+
+using namespace KAuth;
+
+#include <QDebug>
+#include <QVariant>
+
+
+SystemdCommunicator::SystemdCommunicator(QObject *parent) : QObject(parent)
+{
+    setServiceName("fancontrol");
+}
+
+void SystemdCommunicator::setServiceName(const QString &name)
+{
+    if (name != m_serviceName)
+    {
+        m_serviceName = name;
+
+        if (serviceExists())
+        {
+            if (m_serviceInterface)
+            {
+                QDBusConnection::systemBus().disconnect("org.freedesktop.systemd1",
+                                                        m_serviceObjectPath,
+                                                        "org.freedesktop.DBus.Properties",
+                                                        "PropertiesChanged",
+                                                        this,
+                                                        SLOT(updateServiceProperties(QString, QVariantMap, QStringList)));
+                m_serviceInterface->deleteLater();
+            }
+            
+            QVariantList arguments;
+            arguments << QVariant(m_serviceName + ".service");
+            QDBusMessage dbusreply = m_managerInterface->callWithArgumentList(QDBus::AutoDetect, "LoadUnit", arguments);
+            if (dbusreply.type() == QDBusMessage::ErrorMessage)
+            {
+                m_error = dbusreply.errorMessage();
+                emit errorChanged();
+                m_serviceObjectPath.clear();
+            }
+            else
+            {
+                m_serviceObjectPath = qdbus_cast<QDBusObjectPath>(dbusreply.arguments().at(0)).path();
+
+                m_serviceInterface = new QDBusInterface("org.freedesktop.systemd1",
+                                                        m_serviceObjectPath,
+                                                        "org.freedesktop.systemd1.Unit",
+                                                        QDBusConnection::systemBus(),
+                                                        this);
+                QDBusConnection::systemBus().connect("org.freedesktop.systemd1",
+                                                     m_serviceObjectPath,
+                                                     "org.freedesktop.DBus.Properties",
+                                                     "PropertiesChanged",
+                                                     this,
+                                                     SLOT(updateServiceProperties(QString, QVariantMap, QStringList)));
+            }
+        }
+    }
+
+    emit serviceNameChanged();
+    emit serviceEnabledChanged();
+    emit serviceActiveChanged();
+}
+
+bool SystemdCommunicator::serviceExists()
+{
+    if (m_serviceInterface && m_serviceInterface->isValid())
+        return true;
+    
+    QDBusMessage dbusreply;
+
+    if (m_managerInterface && m_managerInterface->isValid())
+        dbusreply = m_managerInterface->call(QDBus::AutoDetect, "ListUnitFiles");
+
+    if (dbusreply.type() == QDBusMessage::ErrorMessage)
+    {
+        m_error = dbusreply.errorMessage();
+        emit errorChanged();
+        return false;
+    }
+    SystemdUnitFileList list = qdbus_cast<SystemdUnitFileList>(dbusreply.arguments().at(0));
+
+    foreach (const SystemdUnitFile &unitFile, list)
+    {
+        if (unitFile.path.contains(m_serviceName + ".service"))
+        {
+            m_error = "Success";
+            emit errorChanged();
+            return true;
+        }
+    }
+
+    m_error = "Service " + m_serviceName + " doesn't exist";
+    emit errorChanged();
+    qDebug() << "Service does not exist!";
+    return false;
+}
+
+bool SystemdCommunicator::serviceActive()
+{
+    if (serviceExists())
+    {
+        if (m_serviceInterface->property("ActiveState").toString() == "active")
+            return true;
+    }
+    return false;
+}
+
+bool SystemdCommunicator::serviceEnabled()
+{
+    if (serviceExists())
+    {
+        if (m_serviceInterface->property("UnitFileState").toString() == "enabled")
+        {
+            return true;
+        }
+    }
+    return false;
+}
+
+void SystemdCommunicator::setServiceEnabled(bool enabled)
+{
+    if (serviceExists() && enabled != serviceEnabled())
+    {
+        QVariantList arguments;
+        QStringList files = QStringList() << m_serviceName + ".service";
+
+        if (enabled)
+        {
+            arguments << files << false << true;
+            dbusAction("EnableUnitFiles", arguments);
+            dbusAction("Reload");
+        }
+        else
+        {
+            arguments << files << false;
+            dbusAction("DisableUnitFiles", arguments);
+            dbusAction("Reload");
+        }
+        emit serviceEnabledChanged();
+    }
+}
+
+void SystemdCommunicator::setServiceActive(bool active)
+{
+    if (serviceExists())
+    {
+        if (active && !serviceActive())
+        {
+            QVariantList args;
+            args << m_serviceName + ".service" << "replace";
+            dbusAction("ReloadOrRestartUnit", args);
+            emit serviceActiveChanged();
+        }
+        else if (!active && serviceActive()) 
+        {
+            QVariantList args;
+            args << m_serviceName + ".service" << "replace";
+            dbusAction("StopUnit", args);
+            emit serviceActiveChanged();
+        }
+        else 
+            qDebug() << "ServiceActive already " << active;
+    }
+}
+
+void SystemdCommunicator::dbusAction(const QString &method, const QVariantList &arguments)
+{
+    QDBusMessage dbusreply;
+
+    if (m_managerInterface && m_managerInterface->isValid())
+    {
+        if (arguments.isEmpty())
+            dbusreply = m_managerInterface->call(QDBus::AutoDetect, method);
+        else
+            dbusreply = m_managerInterface->callWithArgumentList(QDBus::AutoDetect, method, arguments);
+    }
+
+    if (dbusreply.type() == QDBusMessage::ErrorMessage)
+    {
+#ifndef NO_KF5_AUTH
+        if (dbusreply.errorMessage() == "Interactive authentication required.")
+        {
+            Action action("fancontrol.gui.helper.action");
+            action.setHelperId("fancontrol.gui.helper");
+            QVariantMap map;
+            map["action"] = "dbusaction";
+            map["method"] = method;
+            map["arguments"] = arguments;
+            action.setArguments(map);
+
+            ExecuteJob *reply = action.execute();
+
+            if (!reply->exec())
+            {
+                m_error = reply->errorString();
+                emit errorChanged();
+            }
+            else
+            {
+                m_error = method + " succeeded";
+                emit errorChanged();
+            }
+            return;
+        }
+#endif
+        m_error = dbusreply.errorMessage();
+        emit errorChanged();
+    }
+    else
+    {
+        m_error = method + " succeeded";
+        emit errorChanged();
+    }
+}
+
+void SystemdCommunicator::restartService()
+{
+    if (serviceExists())
+    {
+        QVariantList args;
+        args << m_serviceName + ".service" << "replace";
+        dbusAction("ReloadOrRestartUnit", args);
+    }
+}
+
+void SystemdCommunicator::updateServiceProperties(QString, QVariantMap propchanged, QStringList)
+{ 
+    qDebug() << "Properties changed...";
+    
+    if (propchanged.value("ActiveState").isValid())
+    {
+        emit serviceActiveChanged();
+        qDebug() << "Active changed...";
+    }
+    if (propchanged.value("UnitFileState").isValid())
+    {
+        emit serviceEnabledChanged();
+        qDebug() << "Enabled changed...";
+    }
+}
+
+
+QDBusArgument& operator <<(QDBusArgument &argument, const SystemdUnitFile &unitFile)
+{
+    argument.beginStructure();
+    argument << unitFile.path << unitFile.state;
+    argument.endStructure();
+    return argument;
+}
+
+const QDBusArgument& operator >>(const QDBusArgument &argument, SystemdUnitFile &unitFile)
+{
+    argument.beginStructure();
+    argument >> unitFile.path >> unitFile.state;
+    argument.endStructure();
+    return argument;
+}

+ 93 - 0
lib/src/systemdcommunicator.h

@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2015  Malte Veerman <maldela@halloarsch.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#ifndef SYSTEMDCOMMUNICATOR_H
+#define SYSTEMDCOMMUNICATOR_H
+
+#include <QObject>
+#include <QtDBus/QDBusArgument>
+#include <QtDBus/QDBusInterface>
+
+#include "fancontrol_gui_lib_export.h"
+
+class FANCONTROL_GUI_LIB_EXPORT SystemdCommunicator : public QObject
+{
+    Q_OBJECT
+    Q_PROPERTY(QString serviceName READ serviceName WRITE setServiceName NOTIFY serviceNameChanged)
+    Q_PROPERTY(QString error READ error NOTIFY errorChanged)
+    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)
+
+public:
+
+    explicit SystemdCommunicator(QObject *parent = 0);
+
+    QString serviceName() const { return m_serviceName; }
+    void setServiceName(const QString &name);
+    bool serviceExists();
+    bool serviceEnabled();
+    bool serviceActive();
+    void setServiceEnabled(bool enabled);
+    void setServiceActive(bool active);
+    QString error() const { return m_error; }
+    Q_INVOKABLE void dbusAction(const QString &method, const QVariantList &arguments = QVariantList());
+    Q_INVOKABLE void restartService();
+
+
+signals:
+
+    void serviceNameChanged();
+    void serviceEnabledChanged();
+    void serviceActiveChanged();
+    void errorChanged();
+
+
+protected slots:
+    
+    void updateServiceProperties(QString, QVariantMap, QStringList);
+    
+    
+protected:
+
+    QString m_serviceName;
+    QString m_serviceObjectPath;
+    QString m_error = "Success";
+    QDBusInterface *m_managerInterface = new QDBusInterface("org.freedesktop.systemd1",
+                                                            "/org/freedesktop/systemd1",
+                                                            "org.freedesktop.systemd1.Manager",
+                                                            QDBusConnection::systemBus(),
+                                                            this);
+    QDBusInterface *m_serviceInterface = Q_NULLPTR;
+};
+
+typedef struct
+{
+    QString path;
+    QString state;
+} SystemdUnitFile;
+Q_DECLARE_METATYPE(SystemdUnitFile)
+
+typedef QList<SystemdUnitFile> SystemdUnitFileList;
+Q_DECLARE_METATYPE(SystemdUnitFileList)
+
+QDBusArgument &operator <<(QDBusArgument &argument, const SystemdUnitFile &unitFile);
+const QDBusArgument &operator >>(const QDBusArgument &argument, SystemdUnitFile &unitFile);
+
+#endif // SYSTEMDCOMMUNICATOR_H

+ 60 - 0
package/contents/scripts/arrayfunctions.js

@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2015  Malte Veerman <maldela@halloarsch.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+function names(array) {
+    var names = [];
+    for (var i=0; i<array.length; i++) {
+        names[i] = array[i].name;
+    }
+    return names;
+}
+
+function labels(array) {
+    var labels = [];
+    for (var i=0; i<array.length; i++) {
+        labels[i] = array[i].label;
+    }
+    return labels;
+}
+
+function allPwmFans(hwmons) {
+    var pwmFans = [];
+    for (var i=0; i<hwmons.length; i++) {
+        for (var j=0; j<hwmons[i].pwmFans.length; j++) {
+            pwmFans[pwmFans.length] = hwmons[i].pwmFans[j];
+        }
+    }
+    return pwmFans;
+}
+
+function maxProperty(array, prop) {
+    var max = 0;
+    for (var i=0; i<array.length; i++) {
+        max = Math.max(max, array[i][prop]);
+    }
+    return max;
+}
+
+function minProperty(array, prop) {
+    var min = 0;
+    for (var i=0; i<array.length; i++) {
+        min = Math.min(min, array[i][prop]);
+    }
+    return min;
+}

+ 38 - 0
package/contents/scripts/coordinates.js

@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2015  Malte Veerman <maldela@halloarsch.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+function absoluteCoordinatesOf(item) {
+    if (typeof item === "undefined") {
+        item = this;
+    }
+    var offset = Qt.point(0, 0);
+    while (item.parent) {
+        offset.x += item.x;
+        offset.y += item.y;
+        item = item.parent
+    }
+    return offset;
+}
+
+function centerOf(item) {
+    var p = Qt.point(0, 0);
+    p.x = item.x + item.width / 2;
+    p.y = item.y + item.height / 2;
+    return p;
+}

+ 27 - 0
package/contents/scripts/math.js

@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2015  Malte Veerman <maldela@halloarsch.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+function bound(floor, value, ceiling) {
+    if (value >= floor) {
+        if (value <= ceiling) return value;
+        else return ceiling;
+    }
+    return floor;
+}
+

+ 37 - 0
package/contents/scripts/units.js

@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2015  Malte Veerman <maldela@halloarsch.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+function round(number, dec) {
+    if (!dec) dec = 5;
+    return Math.round(number*10*dec) / (10*dec);
+}
+
+function toCelsius(degrees, currentUnit) {
+    var float = parseFloat(degrees);
+    if (currentUnit == 1) { return float + 273.15; }
+    if (currentUnit == 2) { return (float - 32) * 5 / 9; }
+    return float;
+}
+
+function fromCelsius(degrees, newUnit) {
+    var float = parseFloat(degrees);
+    if (newUnit == 1) { return round(float - 273.15); }
+    if (newUnit == 2) { return round(float * 9 / 5 + 32); }
+    return round(float);
+}

+ 206 - 0
package/contents/ui/Application.qml

@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2015  Malte Veerman <maldela@halloarsch.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+import QtQuick 2.4
+import QtQuick.Controls 1.3
+import QtQuick.Window 2.2
+import QtQuick.Dialogs 1.2
+import QtQuick.Layouts 1.1
+
+ApplicationWindow {    
+    id: window
+    title: i18n("Fancontrol-GUI")
+    width: 1024
+    height: 768
+    visible: true
+
+    menuBar: MenuBar {
+        Menu {
+            title: i18n("File")
+            MenuItem {
+                text: i18n("Load configuration file")
+                onTriggered: openFileDialog.open()
+            }
+            MenuItem {
+                text: i18n("Save configuration file")
+                onTriggered: gui.loader.save()
+            }
+            MenuItem {
+                text: i18n("Save configuration file as")
+                onTriggered: saveFileDialog.open()
+            }
+            MenuItem {
+                text: i18n("Exit")
+                onTriggered: Qt.quit()
+            }
+        }
+    }
+
+    toolBar: ToolBar {
+        RowLayout {
+            anchors.fill: parent
+
+            ToolButton {
+                iconName: "document-open"
+                onClicked: openFileDialog.open()
+
+                ToolTip {
+                    text: i18n("Load configuration file")
+                }
+            }
+            ToolButton {
+                iconName: "document-save"
+                onClicked: gui.loader.save()
+
+                ToolTip {
+                    text: i18n("Save configuration file")
+                }
+            }
+            Loader {
+                active: gui.hasSystemdCommunicator()
+                sourceComponent: ToolButton {
+                    iconName: gui.systemdCom.serviceActive ? "system-reboot" : "system-run"
+                    onClicked: gui.systemdCom.serviceActive ? gui.systemdCom.restartService() : gui.systemdCom.serviceActive = true;
+
+                    ToolTip {
+                        text: gui.systemdCom.serviceActive ? i18n("Restart fancontrol") : i18n("Start fancontrol")
+                    }
+                }
+            }
+            Loader {
+                active: gui.hasSystemdCommunicator()
+                sourceComponent: ToolButton {
+                    iconName: "system-shutdown"
+                    enabled: gui.systemdCom.serviceActive
+                    onClicked: gui.systemdCom.serviceActive = false;
+                    
+                    ToolTip {
+                        text: i18n("Stop fancontrol")
+                    }
+                }
+            }
+            Item {
+                Layout.fillWidth: true
+            }
+            Slider {
+                id: sizeSlider
+                Layout.alignment: Qt.AlignRight
+                Layout.preferredWidth: 200
+                value: 0.4
+                visible: tabView.currentIndex == 1
+            }
+        }
+    }
+
+    TabView {
+        property real minTemp: 30.0
+        property real maxTemp: 90.0
+        property string unit: i18n("Celsius")
+
+        id: tabView
+        anchors.fill: parent
+        anchors.topMargin: 5
+        frameVisible: true
+
+        Tab {
+            title: i18n("Sensors")
+            SensorsTab {
+                loader: gui.loader
+            }
+        }
+        Tab {
+            title: i18n("PwmFans")
+            PwmFansTab {
+                size: sizeSlider.value
+                minTemp: tabView.minTemp
+                maxTemp: tabView.maxTemp
+                unit: tabView.unit
+                loader: gui.loader
+            }
+        }
+        Tab {
+            title: i18n("Configfile")
+            ConfigfileTab {
+                loader: gui.loader
+            }
+        }
+        Tab {
+            title: i18n("Settings")
+            SettingsTab {
+                id: settingsTab
+                interval: gui.loader.interval
+                minTemp: tabView.minTemp
+                maxTemp: tabView.maxTemp
+                onMinTempChanged: tabView.minTemp = minTemp
+                onMaxTempChanged: tabView.maxTemp = maxTemp
+                onUnitChanged: tabView.unit = unit
+                loader: gui.loader
+                systemdCom: gui.hasSystemdCommunicator() ? gui.systemdCom : null
+            }
+        }
+    }
+
+    statusBar: StatusBar {
+        Text {
+            property string systemdError: gui.hasSystemdCommunicator() ? gui.systemdCom.error : ""
+            property string loaderError: gui.loader.error
+
+            color: "red"
+
+            onSystemdErrorChanged: {
+                if (systemdError !== "Success" && systemdError.search("succeeded") == -1)
+                    text = systemdError;
+                else if (loaderError === "Success" || loaderError === "")
+                    text = ""
+            }
+            onLoaderErrorChanged: {
+                if (loaderError !== "Success")
+                    text = loaderError;
+                else if (systemdError === "Success" || systemdError === "")
+                    text = ""
+            }
+        }
+    }
+
+    FileDialog {
+        id: openFileDialog
+        title: i18n("Please choose a configuration file")
+        folder: "file:///etc"
+        selectExisting: true
+        selectMultiple: false
+        modality: Qt.NonModal
+
+        onAccepted: {
+            gui.loader.load(fileUrl);
+        }
+    }
+
+    FileDialog {
+        id: saveFileDialog
+        title: i18n("Save configuration file as")
+        folder: "file:///etc"
+        selectExisting: false
+        selectMultiple: false
+        modality: Qt.NonModal
+
+        onAccepted: {
+            gui.loader.save(fileUrl);
+        }
+    }
+}

+ 54 - 0
package/contents/ui/ConfigfileTab.qml

@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2015  Malte Veerman <maldela@halloarsch.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+import QtQuick 2.4
+import QtQuick.Controls 1.2
+import QtQuick.Layouts 1.1
+import "../scripts/arrayfunctions.js" as ArrayFunctions
+
+ColumnLayout {
+    property QtObject loader
+    
+    anchors.fill: parent
+    anchors.topMargin: 5
+
+    Text {
+        anchors.top: parent.top
+        text: loader ? decodeURIComponent(loader.configUrl) : ""
+    }
+
+    Rectangle {
+        Layout.fillHeight: true
+        Layout.fillWidth: true
+
+        border.width: 1
+        radius: 5
+
+        ScrollView {
+            id: scrollView
+            anchors.fill: parent
+            anchors.margins: 5
+
+            TextEdit {
+                text: loader ? loader.configFile : ""
+                readOnly: true
+            }
+        }
+    }
+}

+ 80 - 0
package/contents/ui/KCM.qml

@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2015  Malte Veerman <maldela@halloarsch.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+
+import QtQuick 2.4
+import QtQuick.Controls 1.3
+import QtQuick.Controls.Styles 1.3
+import QtQuick.Window 2.2
+import QtQuick.Dialogs 1.2
+import QtQuick.Layouts 1.1
+import org.kde.kcm 1.0
+
+TabView {
+    property real minTemp: 30.0
+    property real maxTemp: 90.0
+    property string unit: "Celsius"
+
+    id: tabView
+    anchors.fill: parent
+    anchors.margins: 4
+    frameVisible: true
+    implicitHeight: 480
+    implicitWidth: 640
+    
+    Tab {
+        title: i18n("Sensors")
+        SensorsTab {
+            loader: kcm.loader
+        }
+    }
+    Tab {
+        title: i18n("PwmFans")
+        PwmFansTab {
+            size: sizeSlider.value
+            minTemp: tabView.minTemp
+            maxTemp: tabView.maxTemp
+            unit: tabView.unit
+            loader: kcm.loader
+        }
+    }
+    Tab {
+        title: i18n("Configfile")
+        ConfigfileTab {
+            loader: kcm.loader
+        }
+    }
+    Tab {
+        title: i18n("Settings")
+        SettingsTab {
+            id: settingsTab
+            minTemp: tabView.minTemp
+            maxTemp: tabView.maxTemp
+            onMinTempChanged: tabView.minTemp = minTemp
+            onMaxTempChanged: tabView.maxTemp = maxTemp
+            onUnitChanged: tabView.unit = unit
+            loader: kcm.loader
+            systemdCom: kcm.systemdCom
+        }
+    }
+    
+    SystemPalette {
+        id: palette
+    }
+}

+ 65 - 0
package/contents/ui/OptionInput.qml

@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2015  Malte Veerman <maldela@halloarsch.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+import QtQuick 2.4
+import QtQuick.Layouts 1.1
+
+FocusScope {
+    property alias text: value.text
+    property alias font: value.font
+    property alias inputMethodHints: value.inputMethodHints
+    property alias color: value.color
+    property real margin: 6
+
+    implicitHeight: value.implicitHeight + margin*2
+    implicitWidth: value.implicitWidth + margin*2
+
+    Rectangle {
+        id: rect
+        anchors.fill: parent
+        border.width: 1
+        radius: 2
+        border.color: enabled ? palette.text : disabledPalette.text
+
+        TextInput {
+            id: value
+            anchors.fill: parent
+            anchors.leftMargin: margin
+            horizontalAlignment: TextEdit.AlignLeft
+            verticalAlignment: TextEdit.AlignVCenter
+            renderType: Text.NativeRendering
+            selectByMouse: true
+            color: enabled ? palette.text : disabledPalette.text
+
+            MouseArea {
+                anchors.fill: parent
+                cursorShape: Qt.IBeamCursor
+                acceptedButtons: Qt.NoButton
+            }
+        }
+
+        SystemPalette {
+            id: palette
+        }
+        SystemPalette {
+            id: disabledPalette
+            colorGroup: SystemPalette.Disabled
+        }
+    }
+}

+ 441 - 0
package/contents/ui/PwmFan.qml

@@ -0,0 +1,441 @@
+/*
+ * Copyright (C) 2015  Malte Veerman <maldela@halloarsch.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+import QtQuick 2.4
+import QtQuick.Controls 1.3
+import QtQuick.Controls.Styles 1.2
+import QtQuick.Layouts 1.1
+import QtQuick.Window 2.2
+import "../scripts/arrayfunctions.js" as ArrayFunctions
+import "../scripts/math.js" as MoreMath
+import "../scripts/units.js" as Units
+import "../scripts/coordinates.js" as Coordinates
+
+Rectangle {
+    property QtObject fan
+    property QtObject loader
+    property real minTemp: 30.0
+    property real maxTemp: 90.0
+    property int margin: 5
+    property int minimizeDuration: 400
+    property real hwRatio
+    property string unit: "Celsius"
+
+    id: root
+    height: width * hwRatio
+    color: "transparent"
+    border.color: "black"
+    border.width: 2
+    radius: 10
+    clip: false
+    state: fan.active ? "" : "minimized"
+
+    function update() {
+        hasTempCheckBox.checked = fan.hasTemp;
+        fanOffCheckBox.checked = (fan.minPwm == 0);
+        minStartInput.text = fan.minStart;
+        hwmonBox.currentIndex = fan.temp ? fan.temp.parent.index : 0;
+        tempBox.currentIndex = fan.temp ? fan.temp.index - 1 : 0;
+        canvas.requestPaint();
+    }
+    
+    onFanChanged: update();
+    onLoaderChanged: update();
+    
+    Connections {
+        target: loader
+        onConfigUrlChanged: update()
+    }
+
+    states: [
+        State {
+            name: "minimized"
+
+            PropertyChanges {
+                target: root
+                height: header.height + 2*margin
+            }
+        },
+        State {
+            name: "maximized"
+
+//            PropertyChanges {
+//                target: root
+//                height:
+//            }
+        }
+    ]
+    transitions: Transition {
+        NumberAnimation {
+            target: root
+            property: "height"
+            easing.amplitude: 1.5
+            easing.type: Easing.InOutQuad
+            duration: minimizeDuration
+        }
+    }
+
+    SystemPalette {
+        id: palette
+    }
+    SystemPalette {
+        id: disabledPalette
+        colorGroup: SystemPalette.Disabled
+    }
+
+    RowLayout {
+        id: header
+        anchors {
+            left: parent.left
+            leftMargin: margin
+            right: parent.right
+            rightMargin: margin
+            top: parent.top
+            topMargin: margin
+        }
+        z: -1
+        clip: true
+        spacing: margin
+
+        TextEdit {
+            id: nameField
+            Layout.alignment: Qt.AlignTop
+            text: fan.name
+            onTextChanged: fan.name = text;
+            horizontalAlignment: TextEdit.AlignLeft
+            wrapMode: TextEdit.Wrap
+            selectByMouse: true
+            Layout.fillWidth: true
+
+            MouseArea {
+                anchors.fill: parent
+                cursorShape: Qt.IBeamCursor
+                acceptedButtons: Qt.NoButton
+            }
+        }
+
+        Rectangle {
+            id: collapseButton
+            height: 16
+            width: 16
+            Layout.alignment: Qt.AlignTop
+            color: collapseMouseArea.containsMouse ? "red" : "transparent"
+            radius: width / 2
+
+            Text {
+                anchors.fill: parent
+                text: root.state == "minimized" ? "-" : "X"
+                color: collapseMouseArea.containsMouse ? "black" : "red"
+                horizontalAlignment: Text.AlignHCenter
+                verticalAlignment: Text.AlignVCenter
+            }
+
+            MouseArea {
+                id: collapseMouseArea
+                anchors.fill: parent
+                hoverEnabled: true
+                acceptedButtons: Qt.LeftButton
+                onClicked: fan.active = fan.active ? false : true
+            }
+        }
+    }
+
+    Canvas {
+        property int leftPadding: 40 * Screen.devicePixelRatio
+        property int rightPadding: 20 * Screen.devicePixelRatio
+        property int topPadding: 10 * Screen.devicePixelRatio
+        property int bottomPadding: 20 * Screen.devicePixelRatio
+        property int plotWidth: width - leftPadding - rightPadding
+        property int plotHeight: height - topPadding - bottomPadding
+        property alias minTemp: root.minTemp
+        property alias maxTemp: root.maxTemp
+
+        id: canvas
+        renderTarget: Canvas.FramebufferObject
+        anchors {
+            left: parent.left
+            right: parent.right
+            top: header.bottom
+            bottom: settingsArea.top
+        }
+        opacity: state == "minimized" ? 0 : 1
+
+        Behavior on opacity {
+            NumberAnimation { duration: minimizeDuration / 2 }
+        }
+
+        Rectangle {
+            id: currentPwm
+            x: parent.scaleX(fan.temp ? fan.temp.value : minTemp) - width/2
+            y: parent.scaleY(fan.pwm) - height/2
+            width: 7
+            height: width
+            radius: width / 2
+            color: "black"
+            visible: parent.contains(Coordinates.centerOf(this))
+        }
+        PwmPoint {
+            id: stopPoint
+            color: "blue"
+            drag.maximumX: Math.min(canvas.scaleX(canvas.scaleTemp(maxPoint.x)-1), maxPoint.x-1)
+            drag.minimumY: Math.max(canvas.scaleY(canvas.scalePwm(maxPoint.y)-1), maxPoint.y+1)
+            x: canvas.scaleX(MoreMath.bound(minTemp, fan.minTemp, maxTemp)) - width/2
+            y: canvas.scaleY(fan.minStop) - height/2
+            visible: parent.contains(Coordinates.centerOf(this))
+            drag.onActiveChanged: {
+                if (!drag.active) {
+                    fan.minStop = canvas.scalePwm(centerY);
+                    fan.minTemp = canvas.scaleTemp(centerX);
+                    if (!fanOffCheckBox.checked) fan.minPwm = fan.minStop;
+                }
+            }
+        }
+        PwmPoint {
+            id: maxPoint
+            color: "red"
+            drag.minimumX: stopPoint.x
+            drag.maximumY: stopPoint.y
+            x: canvas.scaleX(MoreMath.bound(minTemp, fan.maxTemp, maxTemp)) - width/2
+            y: canvas.scaleY(fan.maxPwm) - height/2
+            visible: parent.contains(Coordinates.centerOf(this))
+            drag.onActiveChanged: {
+                if (!drag.active) {
+                    fan.maxPwm = canvas.scalePwm(centerY);
+                    fan.maxTemp = canvas.scaleTemp(centerX);
+                }
+            }
+        }
+
+        function scaleX(temp) {
+            var scaledX = (temp - minTemp) * plotWidth / (maxTemp - minTemp);
+            return leftPadding + scaledX;
+        }
+        function scaleY(pwm) {
+            var scaledY = pwm * plotHeight / 255;
+            return height - bottomPadding - scaledY;
+        }
+        function scaleTemp(x) {
+            var scaledTemp = (x - leftPadding) / plotWidth * (maxTemp - minTemp);
+            return  minTemp + scaledTemp;
+        }
+        function scalePwm(y) {
+            var scaledPwm = (y - topPadding) / plotHeight * 255;
+            return 255 - scaledPwm;
+        }
+
+        onPaint: {
+            var c = canvas.getContext("2d");
+
+            c.clearRect(0, 0, width, height);
+            c.fillStyle = palette.base;
+            c.fillRect(leftPadding, topPadding, plotWidth, plotHeight);
+
+            var fillGradient = c.createLinearGradient(0, 0, width, 0);
+            fillGradient.addColorStop(0, "rgb(0, 0, 255)");
+            fillGradient.addColorStop(1, "rgb(255, 0, 0)");
+            var strokeGradient = c.createLinearGradient(0, 0, width, 0);
+            strokeGradient.addColorStop(0, "rgb(0, 0, 255)");
+            strokeGradient.addColorStop(1, "rgb(255, 0, 0)");
+            c.fillStyle = fillGradient;
+            c.strokeStyle = strokeGradient;
+            c.lineWidth = 2;
+            c.lineJoin = "round";
+            c.beginPath();
+            if (fanOffCheckBox.checked) {
+                c.moveTo(scaleX(minTemp), scaleY(0));
+                c.lineTo(stopPoint.centerX, scaleY(0));
+            } else {
+                c.moveTo(scaleX(minTemp), stopPoint.centerY);
+            }
+            c.lineTo(stopPoint.centerX, stopPoint.centerY);
+            c.lineTo(maxPoint.centerX, maxPoint.centerY);
+            c.lineTo(scaleX(maxTemp), maxPoint.centerY);
+            c.stroke();
+            c.lineTo(scaleX(maxTemp), height - bottomPadding);
+            c.lineTo(leftPadding, height - bottomPadding);
+            c.fill();
+            fillGradient = c.createLinearGradient(0, 0, 0, height);
+            fillGradient.addColorStop(0, "rgba(127, 127, 127, 0.6)");
+            fillGradient.addColorStop(1, "rgba(127, 127, 127, 0.9)");
+            c.fillStyle = fillGradient;
+            c.fill();
+            c.closePath();
+
+            c.textAlign = "right";
+            c.textBaseline = "middle";
+            c.strokeStyle = palette.dark;
+            c.fillStyle = palette.dark;
+            c.lineWidth = 1;
+            c.strokeRect(leftPadding-0.5, topPadding-0.5, plotWidth+0.5, plotHeight+1.5);
+            for (var i=0; i<=100; i+=20) {
+                var y = scaleY(i*2.55);
+                c.fillText(i + '%', leftPadding - 2, y);
+                if (i != 0 && i != 100) {
+                    for (var j=leftPadding; j<=width-rightPadding; j+=15) {
+                        c.moveTo(j, y);
+                        c.lineTo(Math.min(j+5, width-rightPadding), y);
+                    }
+                    c.stroke();
+                }
+            }
+            c.textAlign = "center";
+            c.textBaseline = "top";
+            var convertedMinTemp = Units.fromCelsius(minTemp, unit);
+            var convertedMaxTemp = Units.fromCelsius(maxTemp, unit);
+            for (i=convertedMinTemp; i<convertedMaxTemp; i+= 10) {
+                var x = scaleX(Units.toCelsius(i, unit));
+                c.fillText(i + '°', x, height-15);
+                if (i != convertedMinTemp) {
+                    for (var j=scaleY(255); j<=scaleY(0); j+=20) {
+                        c.moveTo(x, j);
+                        c.lineTo(x, Math.min(j+5, width-rightPadding));
+                    }
+                    c.stroke();
+                }
+            }
+            c.fillText(convertedMaxTemp + '°', scaleX(maxTemp), height-15);
+        }
+    }
+
+    ColumnLayout {
+        property int padding: 10
+
+        id: settingsArea
+        anchors {
+            left: parent.left
+            leftMargin: padding
+            right: parent.right
+            rightMargin: padding
+            bottom: parent.bottom
+            bottomMargin: padding
+        }
+        visible: root.height >= header.height + height + 2*margin
+        opacity: canvas.opacity
+        clip: true
+        spacing: 0
+
+        RowLayout {
+            spacing: 0
+            height: hwmonBox.height
+            anchors.left: parent.left
+            anchors.right: parent.right
+
+            CheckBox {
+                id: hasTempCheckBox
+                text: "Controlled by:"
+                checked: fan.hasTemp
+                Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
+                onCheckedChanged: fan.hasTemp = checked
+            }
+            Item {
+                Layout.fillWidth: true
+            }
+            ComboBox {
+                property var hwmon: loader.hwmons[currentIndex]
+
+                id: hwmonBox
+                Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
+                model: ArrayFunctions.names(loader.hwmons)
+                enabled: hasTempCheckBox.checked
+            }
+            Text {
+                text: "/"
+                color: enabled ? palette.text : disabledPalette.text
+                Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
+                verticalAlignment: Text.AlignVCenter
+                enabled: hasTempCheckBox.checked
+                renderType: Text.NativeRendering
+            }
+            ComboBox {
+                id: tempBox
+                Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
+                model: ArrayFunctions.names(hwmonBox.hwmon.temps)
+                enabled: hasTempCheckBox.checked
+                onCurrentIndexChanged: { 
+                    if (hasTempCheckBox.checked)
+                        fan.temp = hwmonBox.hwmon.temps[currentIndex];
+                }
+                onModelChanged: {
+                    if (hasTempCheckBox.checked)
+                        fan.temp = hwmonBox.hwmon.temps[currentIndex];
+                }
+            }
+        }
+
+        CheckBox {
+            id: fanOffCheckBox
+            text: "Turn Fan off if temp < MINTEMP"
+            enabled: hasTempCheckBox.checked
+            checked: fan.minPwm == 0
+            onCheckedChanged: {
+                fan.minPwm = checked ? 0 : fan.minStop;
+                canvas.requestPaint();
+            }
+        }
+
+        RowLayout {
+            anchors.left: parent.left
+            anchors.right: parent.right
+
+            Text {
+                text: "Pwm value for fan to start:"
+                Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
+                enabled: fanOffCheckBox.checked && fanOffCheckBox.enabled
+                color: enabled ? palette.text : disabledPalette.text
+                renderType: Text.NativeRendering
+            }
+            OptionInput {
+                id: minStartInput
+                anchors.right: parent.right
+                width: 50
+                enabled: fanOffCheckBox.checked && fanOffCheckBox.enabled
+                text: fan.minStart
+                onTextChanged: fan.minStart = text
+            }
+        }
+
+        RowLayout {
+            anchors.left: parent.left
+            anchors.right: parent.right
+
+            Text {
+                text: "Test start and stop values"
+                Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
+                enabled: fanOffCheckBox.checked && fanOffCheckBox.enabled
+                color: enabled ? palette.text : disabledPalette.text
+                renderType: Text.NativeRendering
+            }
+            Button {
+                text: fan.testing? "Abort" : "Test"
+                anchors.right: parent.right
+                height: hwmonBox.height
+                onClicked: {
+                    if (fan.testing) {
+                        systemdCom.dbusAction("StartUnit", [systemdCom.serviceName + ".service", "replace"]);
+                        fan.abortTesting();
+                    } else {
+                        systemdCom.dbusAction("StopUnit", [systemdCom.serviceName + ".service", "replace"]);
+                        minStartInput.text = Qt.binding(function() { return fan.minStart });
+                        fan.test();
+                    }
+                }
+            }
+        }
+    }
+}

+ 63 - 0
package/contents/ui/PwmFansTab.qml

@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2015  Malte Veerman <maldela@halloarsch.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+import QtQuick 2.4
+import QtQuick.Controls 1.2
+import "../scripts/arrayfunctions.js" as ArrayFunctions
+
+ScrollView {
+    property QtObject loader
+    property real size: 1.0
+    property real minTemp: 30.0
+    property real maxTemp: 90.0
+    property string unit: "Celsius"
+
+    id: scrollView
+    anchors.fill: parent
+    anchors.topMargin: 5
+
+    Flow {
+        spacing: 20 * size
+        width: scrollView.viewport.width
+        move: Transition {
+            NumberAnimation {
+                easing.type: Easing.OutQuad
+                properties: "x,y"
+                duration: 300
+            }
+        }
+
+        Repeater {
+            property var fans: loader ? ArrayFunctions.allPwmFans(loader.hwmons) : []
+
+            id: repeater
+            model: fans.length
+
+            PwmFan {
+                width: 1000 * size
+                hwRatio: 0.8
+                fan: repeater.fans[index]
+                loader: loader
+                minTemp: scrollView.minTemp
+                maxTemp: scrollView.maxTemp
+                unit: scrollView.unit
+            }
+        }
+    }
+}

+ 77 - 0
package/contents/ui/PwmPoint.qml

@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2015  Malte Veerman <maldela@halloarsch.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+import QtQuick 2.4
+import QtQuick.Layouts 1.1
+
+Rectangle {
+    property Item canvas: parent
+    property point center: Qt.point(x + width / 2, y + height / 2);
+    readonly property real centerX: x + width / 2
+    readonly property real centerY: y + height / 2
+    property alias drag: pwmMouse.drag
+
+    id: root
+    width: 10
+    height: width
+    radius: width / 2
+    border.width: pwmMouse.containsMouse || drag.active ? 1 : 0
+
+    onXChanged: parent.requestPaint();
+    onYChanged: parent.requestPaint();
+
+    Drag.dragType: Drag.Automatic
+
+    MouseArea {
+        id: pwmMouse
+        anchors.fill: parent
+        hoverEnabled: canvas.minimized ? false : true
+        drag.target: root
+        drag.axis: Drag.XAndYAxis
+        drag.smoothed: false
+        drag.minimumX: canvas.scaleX(canvas.minTemp) - root.width/2
+        drag.maximumX: canvas.scaleX(canvas.maxTemp) - root.width/2
+        drag.minimumY: canvas.scaleY(255) - root.height/2
+        drag.maximumY: canvas.scaleY(0) - root.height/2
+    }
+
+    Rectangle {
+        id: tooltip
+        x: parent.width
+        y: - height
+        width: Math.max(pwm.width, temp.width)
+        height: pwm.height + temp.height
+        radius: 4
+        color: Qt.rgba(parent.color.r, parent.color.g, parent.color.b, 0.3)
+        visible: pwmMouse.containsMouse || drag.active
+
+        Column {
+            Text {
+                id: pwm
+                font.pointSize: 9
+                text: Math.round(canvas.scalePwm(root.centerY) / 2.55) + '%'
+            }
+            Text {
+                id: temp
+                font.pointSize: 9
+                text: Math.round(canvas.scaleTemp(root.centerX)) + '°'
+            }
+        }
+    }
+}

+ 102 - 0
package/contents/ui/SensorsTab.qml

@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2015  Malte Veerman <maldela@halloarsch.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+import QtQuick 2.4
+import QtQuick.Controls 1.2
+import QtQuick.Layouts 1.1
+import "../scripts/arrayfunctions.js" as ArrayFunctions
+
+RowLayout {
+    property QtObject loader
+    
+    id: root
+    anchors.fill: parent
+    anchors.margins: 2
+    anchors.topMargin: 10
+
+    Repeater {
+        model: loader != null ? loader.hwmons.length : 0
+
+        Rectangle {
+            property QtObject hwmon: loader.hwmons[index]
+            property int padding: 10
+
+            Layout.preferredWidth: root.width / loader.hwmons.length - root.spacing
+            Layout.maximumWidth: 500
+            Layout.fillHeight: true
+            border.width: 1
+            border.color: "black"
+            radius: 5
+
+            Column {
+                id: column
+                anchors.fill: parent
+
+                Text {
+                    anchors.horizontalCenter: parent.horizontalCenter
+                    text: hwmon.name
+                    horizontalAlignment: Text.horizontalCenter
+                }
+
+                Repeater {
+                    model: hwmon.fans.length
+
+                    RowLayout {
+                        width: parent.width
+
+                        Text {
+                            anchors.left: parent.left
+                            anchors.leftMargin: padding
+                            Layout.maximumWidth: parent.width - rpmValue.width - padding*2
+                            clip: true
+                            text: "Fan " + (index+1) + " RPM : "
+                        }
+                        Text {
+                            id: rpmValue
+                            anchors.right: parent.right
+                            anchors.rightMargin: padding
+                            text: hwmon.fans[index].rpm
+                        }
+                    }
+                }
+                Repeater {
+                    model: hwmon.temps.length
+
+                    RowLayout {
+                        width: parent.width
+
+                        Text {
+                            anchors.left: parent.left
+                            anchors.leftMargin: padding
+                            text: hwmon.temps[index].name + ": "
+                            Layout.maximumWidth: parent.width - tempValue.width - padding*2
+                            clip: true
+                        }
+                        Text {
+                            id: tempValue
+                            anchors.right: parent.right
+                            anchors.rightMargin: padding
+                            text: hwmon.temps[index].value
+                        }
+                    }
+                }
+            }
+        }
+    }
+}

+ 180 - 0
package/contents/ui/SettingsTab.qml

@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2015  Malte Veerman <maldela@halloarsch.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+import QtQuick 2.4
+import QtQuick.Layouts 1.1
+import QtQuick.Controls 1.2
+import "../scripts/arrayfunctions.js" as ArrayFunctions
+import "../scripts/units.js" as Units
+
+Item {
+    property QtObject loader
+    property QtObject systemdCom
+    property real minTemp: 30.0
+    property real maxTemp: 90.0
+    property int interval: loader ? loader.interval : 1
+    property int padding: 10
+    property string unit: i18n("Celsius")
+    property real textWidth: 0
+
+    id: root
+    anchors.fill: parent
+    anchors.topMargin: 5
+
+    onIntervalChanged: {
+        if (loader !== null) {
+            var fans = ArrayFunctions.allPwmFans(loader.hwmons);
+            for (var i=0; i<fans.length; i++) {
+                fans[i].interval = interval;
+            }
+        }
+    }
+
+    Column {
+        id: column
+        anchors.fill: parent
+        anchors.margins: padding
+        spacing: 5
+
+        RowLayout {
+            width: parent.width
+
+            Text {
+                Layout.preferredWidth: root.textWidth
+                clip: true
+                text: i18n("Interval:")
+                horizontalAlignment: Text.AlignRight
+                Component.onCompleted: root.textWidth = Math.max(root.textWidth, contentWidth)
+            }
+            OptionInput {
+                id: intervalValue
+                Layout.minimumWidth: implicitWidth
+                Layout.fillWidth: true
+                inputMethodHints: Qt.ImhDigitsOnly
+                text: interval
+                onTextChanged: if (text != "") loader.interval = parseInt(text)
+            }
+        }
+        RowLayout {
+            width: parent.width
+
+            Text {
+                Layout.preferredWidth: root.textWidth
+                clip: true
+                text: i18n("Minimum temperature for fan graphs:")
+                horizontalAlignment: Text.AlignRight
+                Component.onCompleted: root.textWidth = Math.max(root.textWidth, contentWidth)
+            }
+            OptionInput {
+                id: minTempValue
+                Layout.minimumWidth: implicitWidth
+                Layout.fillWidth: true
+                inputMethodHints: Qt.ImhDigitsOnly
+                onTextChanged: if (activeFocus) minTemp = Units.toCelsius(text, unit)
+                Component.onCompleted: text = Units.fromCelsius(minTemp, unit)
+            }
+        }
+        RowLayout {
+            width: parent.width
+
+            Text {
+                Layout.preferredWidth: root.textWidth
+                clip: true
+                text: i18n("Maximum temperature for fan graphs:")
+                horizontalAlignment: Text.AlignRight
+                Component.onCompleted: root.textWidth = Math.max(root.textWidth, contentWidth)
+            }
+            OptionInput {
+                id: maxTempValue
+                Layout.minimumWidth: implicitWidth
+                Layout.fillWidth: true
+                inputMethodHints: Qt.ImhDigitsOnly
+                text: Units.fromCelsius(maxTemp, unit.currentText)
+                onTextChanged: if (activeFocus) maxTemp = Units.toCelsius(text, unit)
+                Component.onCompleted: text = Units.fromCelsius(maxTemp, unit)
+            }
+        }
+        RowLayout {
+            width: parent.width
+
+            Text {
+                Layout.preferredWidth: root.textWidth
+                clip: true
+                text: i18n("Unit:")
+                horizontalAlignment: Text.AlignRight
+                Component.onCompleted: root.textWidth = Math.max(root.textWidth, contentWidth)
+            }
+            ComboBox {
+                id: unitBox
+                Layout.minimumWidth: implicitWidth
+                Layout.fillWidth: true
+                model: [i18n("Celsius"), i18n("Kelvin"), i18n("Fahrenheit")]
+                currentIndex: find(root.unit)
+                onCurrentIndexChanged: {
+                    minTempValue.text = Units.fromCelsius(minTemp, currentIndex);
+                    maxTempValue.text = Units.fromCelsius(maxTemp, currentIndex);
+                }
+            }
+        }
+        Loader {
+            active: systemdCom
+            sourceComponent: RowLayout {
+                width: column.width
+
+                Text {
+                    Layout.preferredWidth: root.textWidth
+                    clip: true
+                    text: i18n("Name of the fancontrol systemd service:")
+                    horizontalAlignment: Text.AlignRight
+                    Component.onCompleted: root.textWidth = Math.max(root.textWidth, contentWidth)
+                }
+                OptionInput {
+                    id: serviceName
+                    Layout.minimumWidth: implicitWidth
+                    Layout.fillWidth: true
+                    color: systemdCom.serviceExists ? "green" : "red"
+                    text: systemdCom.serviceName
+                    onTextChanged: systemdCom.serviceName = text
+                }
+            }
+        }
+        Loader {
+            active: systemdCom
+            sourceComponent: RowLayout {
+                width: column.width
+
+                Text {
+                    Layout.preferredWidth: root.textWidth
+                    clip: true
+                    text: i18n("Fancontrol systemd service autostart:")
+                    horizontalAlignment: Text.AlignRight
+                    Component.onCompleted: root.textWidth = Math.max(root.textWidth, contentWidth)
+                }
+                ComboBox {
+                    id: autostartBox
+                    Layout.minimumWidth: implicitWidth
+                    Layout.fillWidth: true
+                    model: [i18n("disabled") , i18n("enabled")]
+                    currentIndex: systemdCom.serviceEnabled ? 1 : 0
+                    onCurrentIndexChanged: systemdCom.serviceEnabled = (currentIndex == 1) ? true : false
+                }
+            }
+        }
+    }
+}

+ 73 - 0
package/contents/ui/ToolTip.qml

@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2015  Malte Veerman <maldela@halloarsch.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+import QtQuick 2.4
+import "../scripts/coordinates.js" as Coordinates
+
+Rectangle {
+    property alias text: text.text
+    property alias textColor: text.color
+    property alias backgroundColor: rect.color
+    property int delay: 1000
+    property Item target: parent
+    readonly property bool hovered: target.hovered
+
+    id: rect
+    width: text.width + 20
+    height: text.height + 10
+    anchors.top: target.bottom
+    anchors.horizontalCenter: target.horizontalCenter
+    anchors.horizontalCenterOffset: Math.max(0, width/2 - (Coordinates.absoluteCoordinatesOf(target).x+target.width/2))
+    radius: 6
+    color: palette.dark
+    visible: false
+
+    onHoveredChanged: {
+        if (hovered) {
+            if (timer.running)
+                timer.restart();
+            else
+                timer.start();
+        }
+        else
+        {
+            if (timer.running)
+                timer.stop();
+            visible = false;
+        }
+    }
+
+    Text {
+        id: text
+        anchors.centerIn: parent
+        color: palette.highlightedText
+    }
+
+    Timer {
+        id: timer
+        interval: delay
+        repeat: false
+        onTriggered: parent.visible = true
+    }
+
+    SystemPalette {
+        id: palette
+    }
+}
+

+ 16 - 0
package/metadata.desktop

@@ -0,0 +1,16 @@
+[Desktop Entry]
+Name=Fancontrol-GUI-QML-Package
+Comment=Control PWM-fans
+Icon=fancontrol-gui
+Encoding=UTF-8
+Keywords=
+Type=Service
+X-KDE-PluginInfo-Author=Malte Veerman
+X-KDE-PluginInfo-Email=maldela@halloarsch.de
+X-KDE-PluginInfo-License=GPL
+X-KDE-PluginInfo-Name=kcm_fancontrol
+X-KDE-PluginInfo-Version=0.1
+X-KDE-PluginInfo-Website=
+X-KDE-ServiceTypes=Plasma/Generic
+X-Plasma-API=declarativeappletscript 
+X-Plasma-MainScript=ui/KCM.qml


+ 82 - 47
po/de_DE.po

@@ -1,124 +1,159 @@
-# SOME DESCRIPTIVE TITLE.
 # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
 # This file is distributed under the same license as the PACKAGE package.
-# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
 #
+# Malte Veerman <maldela@halloarsch.de>, 2015.
 msgid ""
 msgstr ""
 "Project-Id-Version: fancontrol-gui\n"
 "Report-Msgid-Bugs-To: http://github.com/maldela/fancontrol-gui\n"
-"POT-Creation-Date: 2015-07-07 11:14+0200\n"
-"PO-Revision-Date: 2015-07-07 11:16+0200\n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
-"Language-Team: \n"
+"POT-Creation-Date: 2015-08-04 00:28+0200\n"
+"PO-Revision-Date: 2015-08-04 00:35+0100\n"
+"Last-Translator: Malte Veerman <maldela@halloarsch.de>\n"
+"Language-Team: German <kde-i18n-de@kde.org>\n"
+"Language: de_DE\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
-"X-Generator: Poedit 1.8.2\n"
+"X-Generator: Lokalize 2.0\n"
 "Plural-Forms: nplurals=2; plural=(n != 1);\n"
-"Language: de_DE\n"
 
-#: fancontrol-gui/qml/fancontrol-gui.qml:28
+#: fancontrol-gui/package/contents/ui/qml/main.qml:28
+#: fancontrol-gui/src/main.cpp:39
 msgid "Fancontrol-GUI"
 msgstr "Fancontrol-GUI"
 
-#: fancontrol-gui/qml/fancontrol-gui.qml:35
+#: fancontrol-gui/package/contents/ui/qml/main.qml:35
 msgid "File"
 msgstr "Datei"
 
-#: fancontrol-gui/qml/fancontrol-gui.qml:37
-#: fancontrol-gui/qml/fancontrol-gui.qml:66
+#: fancontrol-gui/package/contents/ui/qml/main.qml:37
+#: fancontrol-gui/package/contents/ui/qml/main.qml:64
 msgid "Load configuration file"
 msgstr "Konfigurationsdatei öffnen"
 
-#: fancontrol-gui/qml/fancontrol-gui.qml:41
-#: fancontrol-gui/qml/fancontrol-gui.qml:74
+#: fancontrol-gui/package/contents/ui/qml/main.qml:41
+#: fancontrol-gui/package/contents/ui/qml/main.qml:72
 msgid "Save configuration file"
 msgstr "Konfigurationsdatei speichern"
 
-#: fancontrol-gui/qml/fancontrol-gui.qml:45
-#: fancontrol-gui/qml/fancontrol-gui.qml:202
+#: fancontrol-gui/package/contents/ui/qml/main.qml:45
+#: fancontrol-gui/package/contents/ui/qml/main.qml:196
 msgid "Save configuration file as"
 msgstr "Konfigurationsdatei speichern unter..."
 
-#: fancontrol-gui/qml/fancontrol-gui.qml:49
+#: fancontrol-gui/package/contents/ui/qml/main.qml:49
 msgid "Exit"
 msgstr "Schließen"
 
-#: fancontrol-gui/qml/fancontrol-gui.qml:85
+#: fancontrol-gui/package/contents/ui/qml/main.qml:82
 msgid "Restart fancontrol"
 msgstr "Fancontrol neustarten"
 
-#: fancontrol-gui/qml/fancontrol-gui.qml:85
+#: fancontrol-gui/package/contents/ui/qml/main.qml:82
 msgid "Start fancontrol"
 msgstr "Fancontrol starten"
 
-#: fancontrol-gui/qml/fancontrol-gui.qml:98
+#: fancontrol-gui/package/contents/ui/qml/main.qml:94
 msgid "Stop fancontrol"
 msgstr "Fancontrol stoppen"
 
-#: fancontrol-gui/qml/fancontrol-gui.qml:124
+#: fancontrol-gui/package/contents/ui/qml/main.qml:114
+#: fancontrol-gui/package/contents/ui/qml/SettingsTab.qml:33
+#: fancontrol-gui/package/contents/ui/qml/SettingsTab.qml:127
+msgid "Celsius"
+msgstr "Celsius"
+
+#: fancontrol-gui/package/contents/ui/qml/main.qml:122
+#: kcm/package/contents/ui/main.qml:43
 msgid "Sensors"
 msgstr "Sensoren"
 
-#: fancontrol-gui/qml/fancontrol-gui.qml:128
+#: fancontrol-gui/package/contents/ui/qml/main.qml:128
+#: kcm/package/contents/ui/main.qml:49
 msgid "PwmFans"
 msgstr "PwmLüfter"
 
-#: fancontrol-gui/qml/fancontrol-gui.qml:137
+#: fancontrol-gui/package/contents/ui/qml/main.qml:138
+#: kcm/package/contents/ui/main.qml:59
 msgid "Configfile"
 msgstr "Konfigurationsdatei"
 
-#: fancontrol-gui/qml/fancontrol-gui.qml:142
+#: fancontrol-gui/package/contents/ui/qml/main.qml:144
+#: kcm/package/contents/ui/main.qml:65
 msgid "Settings"
 msgstr "Einstellungen"
 
-#: fancontrol-gui/qml/fancontrol-gui.qml:189
+#: fancontrol-gui/package/contents/ui/qml/main.qml:183
 msgid "Please choose a configuration file"
 msgstr "Konfigurationsdatei auswählen"
 
-#: rc.cpp:1
-msgctxt "NAME OF TRANSLATORS"
-msgid "Your names"
-msgstr "Malte Veerman"
-
-#: rc.cpp:2
-msgctxt "EMAIL OF TRANSLATORS"
-msgid "Your emails"
-msgstr "maldela@halloarsch.de"
-
-#: share/qml/SettingsTab.qml:31 share/qml/SettingsTab.qml:131
-msgid "Celsius"
-msgstr "Celsius"
-
-#: share/qml/SettingsTab.qml:64
+#: fancontrol-gui/package/contents/ui/qml/SettingsTab.qml:61
 msgid "Interval:"
 msgstr "Intervall:"
 
-#: share/qml/SettingsTab.qml:84
+#: fancontrol-gui/package/contents/ui/qml/SettingsTab.qml:80
 msgid "Minimum temperature for fan graphs:"
 msgstr "Minimale Temperatur für Lüftergraphen:"
 
-#: share/qml/SettingsTab.qml:104
+#: fancontrol-gui/package/contents/ui/qml/SettingsTab.qml:99
 msgid "Maximum temperature for fan graphs:"
 msgstr "Maximale Temperatur für Lüftergraphen:"
 
-#: share/qml/SettingsTab.qml:125
+#: fancontrol-gui/package/contents/ui/qml/SettingsTab.qml:119
 msgid "Unit:"
 msgstr "Einheit:"
 
-#: share/qml/SettingsTab.qml:131
+#: fancontrol-gui/package/contents/ui/qml/SettingsTab.qml:127
 msgid "Kelvin"
 msgstr "Kelvin"
 
-#: share/qml/SettingsTab.qml:131
+#: fancontrol-gui/package/contents/ui/qml/SettingsTab.qml:127
 msgid "Fahrenheit"
 msgstr "Fahrenheit"
 
-#: share/qml/SettingsTab.qml:149
+#: fancontrol-gui/package/contents/ui/qml/SettingsTab.qml:143
 msgid "Name of the fancontrol systemd service:"
 msgstr "Name des fancontrol systemd services:"
 
-#: share/qml/SettingsTab.qml:170
+#: fancontrol-gui/package/contents/ui/qml/SettingsTab.qml:165
 msgid "Fancontrol systemd service autostart:"
 msgstr "Fancontrol systemd service Autostart:"
+
+#: fancontrol-gui/package/contents/ui/qml/SettingsTab.qml:173
+msgid "disabled"
+msgstr "deaktiviert"
+
+#: fancontrol-gui/package/contents/ui/qml/SettingsTab.qml:173
+msgid "enabled"
+msgstr "aktiviert"
+
+#: fancontrol-gui/src/main.cpp:41
+msgid "Graphical user interface for fancontrol"
+msgstr "Graphische Benutzeroberfläche für fanontrol"
+
+#: fancontrol-gui/src/main.cpp:47 kcm/src/fancontrolkcm.cpp:50
+msgid "Malte Veerman"
+msgstr "Malte Veerman"
+
+#: fancontrol-gui/src/main.cpp:47 kcm/src/fancontrolkcm.cpp:50
+msgid "Main Developer"
+msgstr "Hauptentwickler"
+
+#: kcm/src/fancontrolkcm.cpp:42
+msgid "Fancontrol-KCM"
+msgstr "Fancontrol-KCM"
+
+#: kcm/src/fancontrolkcm.cpp:44
+msgid "KDE Fancontrol Module"
+msgstr "KDE Fancontrol Modul"
+
+#: po/rc.cpp:1 rc.cpp:1
+msgctxt "NAME OF TRANSLATORS"
+msgid "Your names"
+msgstr "Malte Veerman"
+
+#: po/rc.cpp:2 rc.cpp:2
+msgctxt "EMAIL OF TRANSLATORS"
+msgid "Your emails"
+msgstr "maldela@halloarsch.de"
+

+ 48 - 0
po/extract_messages.sh

@@ -0,0 +1,48 @@
+#!/bin/sh
+BASEDIR="../"	                                        # root of translatable sources
+PROJECT="fancontrol-gui"	                        # project name
+BUGADDR="http://github.com/maldela/fancontrol-gui"	# MSGID-Bugs
+WDIR=`pwd`		                                # working dir
+ 
+ 
+echo "Preparing rc files"
+cd ${BASEDIR}
+# we use simple sorting to make sure the lines do not jump around too much from system to system
+find . -name '*.rc' -o -name '*.ui' -o -name '*.kcfg' | sort > ${WDIR}/rcfiles.list
+xargs --arg-file=${WDIR}/rcfiles.list extractrc > ${WDIR}/rc.cpp
+# additional string for KAboutData
+echo 'i18nc("NAME OF TRANSLATORS","Your names");' >> ${WDIR}/rc.cpp
+echo 'i18nc("EMAIL OF TRANSLATORS","Your emails");' >> ${WDIR}/rc.cpp
+cd ${WDIR}
+echo "Done preparing rc files"
+ 
+ 
+echo "Extracting messages"
+cd ${BASEDIR}
+# see above on sorting
+find . -name '*.cpp' -o -name '*.h' -o -name '*.qml' | sort > ${WDIR}/infiles.list
+echo "rc.cpp" >> ${WDIR}/infiles.list
+cd ${WDIR}
+xgettext --from-code=UTF-8 -C -kde -ci18n -ki18n:1 -ki18nc:1c,2 -ki18np:1,2 -ki18ncp:1c,2,3 -ktr2i18n:1 \
+	-kI18N_NOOP:1 -kI18N_NOOP2:1c,2 -kaliasLocale -kki18n:1 -kki18nc:1c,2 -kki18np:1,2 -kki18ncp:1c,2,3 \
+	--msgid-bugs-address="${BUGADDR}" \
+	--files-from=infiles.list -D ${BASEDIR} -D ${WDIR} -o ${PROJECT}.pot || { echo "error while calling xgettext. aborting."; exit 1; }
+echo "Done extracting messages"
+ 
+ 
+echo "Merging translations"
+catalogs=`find . -name '*.po'`
+for cat in $catalogs; do
+  echo $cat
+  msgmerge -o $cat.new $cat ${PROJECT}.pot
+  mv $cat.new $cat
+done
+echo "Done merging translations"
+ 
+ 
+echo "Cleaning up"
+cd ${WDIR}
+rm rcfiles.list
+rm infiles.list
+rm rc.cpp
+echo "Done" 

+ 75 - 40
po/fancontrol-gui.pot

@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: http://github.com/maldela/fancontrol-gui\n"
-"POT-Creation-Date: 2015-07-07 11:06+0200\n"
+"POT-Creation-Date: 2015-08-04 00:28+0200\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -17,107 +17,142 @@ msgstr ""
 "Content-Type: text/plain; charset=CHARSET\n"
 "Content-Transfer-Encoding: 8bit\n"
 
-#: fancontrol-gui/qml/fancontrol-gui.qml:28
+#: fancontrol-gui/package/contents/ui/qml/main.qml:28
+#: fancontrol-gui/src/main.cpp:39
 msgid "Fancontrol-GUI"
 msgstr ""
 
-#: fancontrol-gui/qml/fancontrol-gui.qml:35
+#: fancontrol-gui/package/contents/ui/qml/main.qml:35
 msgid "File"
 msgstr ""
 
-#: fancontrol-gui/qml/fancontrol-gui.qml:37
-#: fancontrol-gui/qml/fancontrol-gui.qml:66
+#: fancontrol-gui/package/contents/ui/qml/main.qml:37
+#: fancontrol-gui/package/contents/ui/qml/main.qml:64
 msgid "Load configuration file"
 msgstr ""
 
-#: fancontrol-gui/qml/fancontrol-gui.qml:41
-#: fancontrol-gui/qml/fancontrol-gui.qml:74
+#: fancontrol-gui/package/contents/ui/qml/main.qml:41
+#: fancontrol-gui/package/contents/ui/qml/main.qml:72
 msgid "Save configuration file"
 msgstr ""
 
-#: fancontrol-gui/qml/fancontrol-gui.qml:45
-#: fancontrol-gui/qml/fancontrol-gui.qml:202
+#: fancontrol-gui/package/contents/ui/qml/main.qml:45
+#: fancontrol-gui/package/contents/ui/qml/main.qml:196
 msgid "Save configuration file as"
 msgstr ""
 
-#: fancontrol-gui/qml/fancontrol-gui.qml:49
+#: fancontrol-gui/package/contents/ui/qml/main.qml:49
 msgid "Exit"
 msgstr ""
 
-#: fancontrol-gui/qml/fancontrol-gui.qml:85
+#: fancontrol-gui/package/contents/ui/qml/main.qml:82
 msgid "Restart fancontrol"
 msgstr ""
 
-#: fancontrol-gui/qml/fancontrol-gui.qml:85
+#: fancontrol-gui/package/contents/ui/qml/main.qml:82
 msgid "Start fancontrol"
 msgstr ""
 
-#: fancontrol-gui/qml/fancontrol-gui.qml:98
+#: fancontrol-gui/package/contents/ui/qml/main.qml:94
 msgid "Stop fancontrol"
 msgstr ""
 
-#: fancontrol-gui/qml/fancontrol-gui.qml:124
+#: fancontrol-gui/package/contents/ui/qml/main.qml:114
+#: fancontrol-gui/package/contents/ui/qml/SettingsTab.qml:33
+#: fancontrol-gui/package/contents/ui/qml/SettingsTab.qml:127
+msgid "Celsius"
+msgstr ""
+
+#: fancontrol-gui/package/contents/ui/qml/main.qml:122
+#: kcm/package/contents/ui/main.qml:43
 msgid "Sensors"
 msgstr ""
 
-#: fancontrol-gui/qml/fancontrol-gui.qml:128
+#: fancontrol-gui/package/contents/ui/qml/main.qml:128
+#: kcm/package/contents/ui/main.qml:49
 msgid "PwmFans"
 msgstr ""
 
-#: fancontrol-gui/qml/fancontrol-gui.qml:137
+#: fancontrol-gui/package/contents/ui/qml/main.qml:138
+#: kcm/package/contents/ui/main.qml:59
 msgid "Configfile"
 msgstr ""
 
-#: fancontrol-gui/qml/fancontrol-gui.qml:142
+#: fancontrol-gui/package/contents/ui/qml/main.qml:144
+#: kcm/package/contents/ui/main.qml:65
 msgid "Settings"
 msgstr ""
 
-#: fancontrol-gui/qml/fancontrol-gui.qml:189
+#: fancontrol-gui/package/contents/ui/qml/main.qml:183
 msgid "Please choose a configuration file"
 msgstr ""
 
-#: rc.cpp:1 rc.cpp:1
-msgctxt "NAME OF TRANSLATORS"
-msgid "Your names"
-msgstr ""
-
-#: rc.cpp:2 rc.cpp:2
-msgctxt "EMAIL OF TRANSLATORS"
-msgid "Your emails"
-msgstr ""
-
-#: share/qml/SettingsTab.qml:31 share/qml/SettingsTab.qml:131
-msgid "Celsius"
-msgstr ""
-
-#: share/qml/SettingsTab.qml:64
+#: fancontrol-gui/package/contents/ui/qml/SettingsTab.qml:61
 msgid "Interval:"
 msgstr ""
 
-#: share/qml/SettingsTab.qml:84
+#: fancontrol-gui/package/contents/ui/qml/SettingsTab.qml:80
 msgid "Minimum temperature for fan graphs:"
 msgstr ""
 
-#: share/qml/SettingsTab.qml:104
+#: fancontrol-gui/package/contents/ui/qml/SettingsTab.qml:99
 msgid "Maximum temperature for fan graphs:"
 msgstr ""
 
-#: share/qml/SettingsTab.qml:125
+#: fancontrol-gui/package/contents/ui/qml/SettingsTab.qml:119
 msgid "Unit:"
 msgstr ""
 
-#: share/qml/SettingsTab.qml:131
+#: fancontrol-gui/package/contents/ui/qml/SettingsTab.qml:127
 msgid "Kelvin"
 msgstr ""
 
-#: share/qml/SettingsTab.qml:131
+#: fancontrol-gui/package/contents/ui/qml/SettingsTab.qml:127
 msgid "Fahrenheit"
 msgstr ""
 
-#: share/qml/SettingsTab.qml:149
+#: fancontrol-gui/package/contents/ui/qml/SettingsTab.qml:143
 msgid "Name of the fancontrol systemd service:"
 msgstr ""
 
-#: share/qml/SettingsTab.qml:170
+#: fancontrol-gui/package/contents/ui/qml/SettingsTab.qml:165
 msgid "Fancontrol systemd service autostart:"
 msgstr ""
+
+#: fancontrol-gui/package/contents/ui/qml/SettingsTab.qml:173
+msgid "disabled"
+msgstr ""
+
+#: fancontrol-gui/package/contents/ui/qml/SettingsTab.qml:173
+msgid "enabled"
+msgstr ""
+
+#: fancontrol-gui/src/main.cpp:41
+msgid "Graphical user interface for fancontrol"
+msgstr ""
+
+#: fancontrol-gui/src/main.cpp:47 kcm/src/fancontrolkcm.cpp:50
+msgid "Malte Veerman"
+msgstr ""
+
+#: fancontrol-gui/src/main.cpp:47 kcm/src/fancontrolkcm.cpp:50
+msgid "Main Developer"
+msgstr ""
+
+#: kcm/src/fancontrolkcm.cpp:42
+msgid "Fancontrol-KCM"
+msgstr ""
+
+#: kcm/src/fancontrolkcm.cpp:44
+msgid "KDE Fancontrol Module"
+msgstr ""
+
+#: po/rc.cpp:1 rc.cpp:1
+msgctxt "NAME OF TRANSLATORS"
+msgid "Your names"
+msgstr ""
+
+#: po/rc.cpp:2 rc.cpp:2
+msgctxt "EMAIL OF TRANSLATORS"
+msgid "Your emails"
+msgstr ""