Kaynağa Gözat

Huge changes all over the place

Malte Veerman 10 yıl önce
ebeveyn
işleme
3adc0052cf
42 değiştirilmiş dosya ile 3968 ekleme ve 170 silme
  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

BIN
po/de_DE.mo


+ 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 ""