diff --git a/CMakeLists.txt b/CMakeLists.txt index 8560684..b1bca54 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,144 +1,145 @@ cmake_minimum_required(VERSION 3.0) project(libksysguard) set(PROJECT_VERSION "5.18.80") set(PROJECT_VERSION_MAJOR 5) # check with non-Plasma consumers (e.g. KDevelop) before bumping these versions set(QT_MIN_VERSION "5.14.0") set(KF5_MIN_VERSION "5.66.0") find_package(ECM ${KF5_MIN_VERSION} REQUIRED NO_MODULE) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH}) include(KDEInstallDirs) include(KDECMakeSettings) include(KDECompilerSettings NO_POLICY_SCOPE) include(KDEClangFormat) include(ECMAddTests) include(ECMInstallIcons) include(ECMSetupVersion) include(ECMQtDeclareLoggingCategory) include(CMakePackageConfigHelpers) include(CheckIncludeFiles) include(CheckLibraryExists) include(FeatureSummary) include(GenerateExportHeader) find_package(Qt5 ${QT_MIN_VERSION} REQUIRED CONFIG COMPONENTS DBus Network Widgets Qml Quick) find_package(Qt5WebEngineWidgets ${QT_MIN_VERSION} CONFIG) set_package_properties(Qt5WebEngineWidgets PROPERTIES URL "git://code.qt.org/qt/qtwebenginewidgets.git" DESCRIPTION "Qt WebEngine module (web browsing engine)" TYPE OPTIONAL PURPOSE "Used by the HTML-based GUI ksysguard library" ) find_package(Qt5WebChannel ${QT_MIN_VERSION} CONFIG) set_package_properties(Qt5WebChannel PROPERTIES URL "git://code.qt.org/qt/qtwebchannel.git" DESCRIPTION "Qt WebChannel module" TYPE OPTIONAL PURPOSE "Used by the HTML-based GUI ksysguard library" ) find_package(KF5 REQUIRED COMPONENTS CoreAddons Config I18n JobWidgets WindowSystem Completion Auth WidgetsAddons IconThemes ConfigWidgets Service GlobalAccel KIO) find_package(KF5 OPTIONAL_COMPONENTS Plasma) set_package_properties(KF5Plasma PROPERTIES URL "https://cgit.kde.org/plasma-framework.git/" DESCRIPTION "The library of the plasma project" TYPE OPTIONAL PURPOSE "Used by signalplotter to use Plasma themes" ) find_package(ZLIB REQUIRED) set_package_properties(ZLIB PROPERTIES DESCRIPTION "Support for gzip compressed files and data streams" URL "http://www.zlib.net" TYPE REQUIRED ) check_library_exists(c clock_gettime "time.h" HAVE_CLOCK_GETTIME_C) ecm_setup_version(${PROJECT_VERSION} VARIABLE_PREFIX KSYSGUARD VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/ksysguard_version.h" PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/KF5SysGuardConfigVersion.cmake" SOVERSION 9 ) find_package(X11) set_package_properties(X11 PROPERTIES DESCRIPTION "X11 libraries" URL "https://www.x.org" TYPE OPTIONAL PURPOSE "Required for building the X11 based workspace" ) if(X11_FOUND) find_package(Qt5X11Extras REQUIRED) find_library(X11_XRes_LIB XRes ${X11_LIB_SEARCH_PATH}) find_path(X11_XRes_INCLUDE_PATH X11/extensions/XRes.h ${X11_INC_SEARCH_PATH}) if(X11_XRes_LIB AND X11_XRes_INCLUDE_PATH) set(X11_XRes_FOUND TRUE) endif() endif() set(WEBENGINE_SCRIPTING_ENABLED FALSE) if(Qt5WebEngineWidgets_FOUND AND Qt5WebChannel_FOUND) set(WEBENGINE_SCRIPTING_ENABLED TRUE) endif() add_feature_info("Scripting plugin support" WEBENGINE_SCRIPTING_ENABLED "Support scripting plugins using WebEngine and WebChannel") set(HAVE_X11 ${X11_FOUND}) set(HAVE_XRES ${X11_XRes_FOUND}) configure_file(config-ksysguard.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-ksysguard.h ) add_definitions(-DQT_NO_URL_CAST_FROM_STRING) add_definitions(-DQT_USE_QSTRINGBUILDER) add_definitions(-DQT_NO_CAST_FROM_ASCII) add_definitions(-DQT_NO_CAST_TO_ASCII) add_definitions(-DQT_NO_NARROWING_CONVERSIONS_IN_CONNECT) if (EXISTS "${CMAKE_SOURCE_DIR}/.git") add_definitions(-DKF_DISABLE_DEPRECATED_BEFORE_AND_AT=0x054200) endif() set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD_REQUIRED TRUE) add_subdirectory( formatter ) add_subdirectory( lsofui ) add_subdirectory( processcore ) add_subdirectory( processui ) +add_subdirectory( sensors ) if (KF5Plasma_FOUND) add_subdirectory( signalplotter ) endif() add_subdirectory( ksgrd ) if(BUILD_TESTING) add_subdirectory( autotests ) endif() # add clang-format target for all our real source files file(GLOB_RECURSE ALL_CLANG_FORMAT_SOURCE_FILES *.cpp *.h) kde_clang_format(${ALL_CLANG_FORMAT_SOURCE_FILES}) install(DIRECTORY scripts/ DESTINATION ${KDE_INSTALL_DATADIR}/ksysguard/scripts) set(CMAKECONFIG_INSTALL_DIR ${KDE_INSTALL_LIBDIR}/cmake/KF5SysGuard) configure_package_config_file(KF5SysGuardConfig.cmake.in "${CMAKE_CURRENT_BINARY_DIR}/KF5SysGuardConfig.cmake" INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR}) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/KF5SysGuardConfig.cmake ${CMAKE_CURRENT_BINARY_DIR}/KF5SysGuardConfigVersion.cmake DESTINATION ${CMAKECONFIG_INSTALL_DIR}) install(EXPORT libksysguardLibraryTargets NAMESPACE KF5:: DESTINATION ${CMAKECONFIG_INSTALL_DIR} FILE KF5SysGuardLibraryTargets.cmake ) install(FILES libksysguard.categories DESTINATION ${KDE_INSTALL_LOGGINGCATEGORIESDIR}) feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/formatter/Formatter.cpp b/formatter/Formatter.cpp index 83e04da..866b46a 100644 --- a/formatter/Formatter.cpp +++ b/formatter/Formatter.cpp @@ -1,435 +1,435 @@ /* - Copyright (C) 2019 Vlad Zagorodniy + Copyright (C) 2019 Vlad Zahorodnii formatBootTimestamp is based on TimeUtil class: Copyright (C) 2014 Gregor Mi This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "Formatter.h" #include #include #include #include #ifdef Q_OS_OSX #include #include #else #include #endif #include namespace KSysGuard { // TODO: Is there a bit nicer way to handle formatting? static KLocalizedString unitFormat(Unit unit) { const static KLocalizedString B = ki18nc("Bytes unit symbol", "%1 B"); const static KLocalizedString KiB = ki18nc("Kilobytes unit symbol", "%1 KiB"); const static KLocalizedString MiB = ki18nc("Megabytes unit symbol", "%1 MiB"); const static KLocalizedString GiB = ki18nc("Gigabytes unit symbol", "%1 GiB"); const static KLocalizedString TiB = ki18nc("Terabytes unit symbol", "%1 TiB"); const static KLocalizedString PiB = ki18nc("Petabytes unit symbol", "%1 PiB"); const static KLocalizedString bps = ki18nc("Bytes per second unit symbol", "%1 B/s"); const static KLocalizedString Kbps = ki18nc("Kilobytes per second unit symbol", "%1 KiB/s"); const static KLocalizedString Mbps = ki18nc("Megabytes per second unit symbol", "%1 MiB/s"); const static KLocalizedString Gbps = ki18nc("Gigabytes per second unit symbol", "%1 GiB/s"); const static KLocalizedString Tbps = ki18nc("Gigabytes per second unit symbol", "%1 TiB/s"); const static KLocalizedString Pbps = ki18nc("Gigabytes per second unit symbol", "%1 PiB/s"); const static KLocalizedString Hz = ki18nc("Hertz unit symbol", "%1 Hz"); const static KLocalizedString kHz = ki18nc("Kilohertz unit symbol", "%1 kHz"); const static KLocalizedString MHz = ki18nc("Megahertz unit symbol", "%1 MHz"); const static KLocalizedString GHz = ki18nc("Gigahertz unit symbol", "%1 GHz"); const static KLocalizedString THz = ki18nc("Terahertz unit symbol", "%1 THz"); const static KLocalizedString PHz = ki18nc("Petahertz unit symbol", "%1 PHz"); const static KLocalizedString percent = ki18nc("Percent unit", "%1%"); const static KLocalizedString RPM = ki18nc("Revolutions per minute unit symbol", "%1 RPM"); const static KLocalizedString C = ki18nc("Celsius unit symbol", "%1°C"); const static KLocalizedString dBm = ki18nc("Decibels unit symbol", "%1 dBm"); const static KLocalizedString s = ki18nc("Seconds unit symbol", "%1s"); const static KLocalizedString V = ki18nc("Volts unit symbol", "%1 V"); const static KLocalizedString W = ki18nc("Watts unit symbol", "%1 W"); const static KLocalizedString rate = ki18nc("Rate unit symbol", "%1 s⁻¹"); const static KLocalizedString unitless = ki18nc("Unitless", "%1"); switch (unit) { case UnitByte: return B; case UnitKiloByte: return KiB; case UnitMegaByte: return MiB; case UnitGigaByte: return GiB; case UnitTeraByte: return TiB; case UnitPetaByte: return PiB; case UnitByteRate: return bps; case UnitKiloByteRate: return Kbps; case UnitMegaByteRate: return Mbps; case UnitGigaByteRate: return Gbps; case UnitTeraByteRate: return Tbps; case UnitPetaByteRate: return Pbps; case UnitHertz: return Hz; case UnitKiloHertz: return kHz; case UnitMegaHertz: return MHz; case UnitGigaHertz: return GHz; case UnitTeraHertz: return THz; case UnitPetaHertz: return PHz; case UnitCelsius: return C; case UnitDecibelMilliWatts: return dBm; case UnitPercent: return percent; case UnitRate: return rate; case UnitRpm: return RPM; case UnitSecond: return s; case UnitVolt: return V; case UnitWatt: return W; default: return unitless; } } static int unitOrder(Unit unit) { switch (unit) { case UnitByte: case UnitKiloByte: case UnitMegaByte: case UnitGigaByte: case UnitTeraByte: case UnitPetaByte: case UnitByteRate: case UnitKiloByteRate: case UnitMegaByteRate: case UnitGigaByteRate: case UnitTeraByteRate: case UnitPetaByteRate: return 1024; case UnitHertz: case UnitKiloHertz: case UnitMegaHertz: case UnitGigaHertz: case UnitTeraHertz: case UnitPetaHertz: return 1000; default: return 0; } } static Unit unitBase(Unit unit) { switch (unit) { case UnitByte: case UnitKiloByte: case UnitMegaByte: case UnitGigaByte: case UnitTeraByte: case UnitPetaByte: return UnitByte; case UnitByteRate: case UnitKiloByteRate: case UnitMegaByteRate: case UnitGigaByteRate: case UnitTeraByteRate: case UnitPetaByteRate: return UnitByteRate; case UnitHertz: case UnitKiloHertz: case UnitMegaHertz: case UnitGigaHertz: case UnitTeraHertz: case UnitPetaHertz: return UnitHertz; default: return unit; } } static Unit adjustedUnit(qreal value, Unit unit, MetricPrefix prefix) { const int order = unitOrder(unit); if (!order) { return unit; } const Unit baseUnit = unitBase(unit); const MetricPrefix basePrefix = MetricPrefix(unit - baseUnit); if (prefix == MetricPrefixAutoAdjust) { const qreal absoluteValue = value * std::pow(order, int(basePrefix)); if (absoluteValue > 0) { const int targetPrefix = std::log2(absoluteValue) / std::log2(order); if (targetPrefix <= MetricPrefixLast) { prefix = MetricPrefix(targetPrefix); } } if (prefix == MetricPrefixAutoAdjust) { prefix = basePrefix; } } return Unit(prefix + baseUnit); } static QString formatNumber(const QVariant &value, Unit unit, MetricPrefix prefix, FormatOptions options) { qreal amount = value.toDouble(); if (!options.testFlag(FormatOptionShowNull) && qFuzzyIsNull(amount)) { return QString(); } const Unit adjusted = adjustedUnit(amount, unit, prefix); if (adjusted != unit) { amount /= std::pow(unitOrder(unit), adjusted - unit); } const int precision = (value.type() != QVariant::Double && adjusted <= unit) ? 0 : 1; const QString text = QLocale().toString(amount, 'f', precision); return unitFormat(adjusted).subs(text).toString(); } static QString formatTime(const QVariant &value) { const qlonglong seconds = value.toLongLong(); const QString minutesString = QString::number(seconds / 60); const QString secondsScring = QStringLiteral("%1").arg(seconds % 60, 2, 10, QLatin1Char('0')); return minutesString + QLatin1Char(':') + secondsScring; } static QString formatBootTimestamp(const QVariant &value, FormatOptions options) { const qlonglong clockTicksSinceSystemBoot = value.toLongLong(); const QDateTime now = QDateTime::currentDateTime(); #ifdef Q_OS_OSX clock_serv_t cclock; mach_timespec_t tp; host_get_clock_service(mach_host_self(), SYSTEM_CLOCK, &cclock); clock_get_time(cclock, &tp); mach_port_deallocate(mach_task_self(), cclock); #else timespec tp; clock_gettime(CLOCK_MONOTONIC, &tp); #endif const QDateTime systemBootTime = now.addSecs(-tp.tv_sec); const long clockTicksPerSecond = sysconf(_SC_CLK_TCK); const qreal secondsSinceSystemBoot = qreal(clockTicksSinceSystemBoot) / clockTicksPerSecond; const QDateTime absoluteStartTime = systemBootTime.addSecs(secondsSinceSystemBoot); if (!options.testFlag(FormatOptionAgo)) { return QLocale().toString(absoluteStartTime); } const qint64 totalSeconds = absoluteStartTime.secsTo(now); const qint64 totalMinutes = totalSeconds / 60.0; const qint64 totalHours = totalSeconds / 60.0 / 60.0; const qint64 totalDays = totalSeconds / 60.0 / 60.0 / 24.0; if (!totalMinutes) { return i18nc("contains a abbreviated time unit: (s)econds", "%1s ago", totalSeconds); } if (!totalHours) { const int seconds = totalSeconds - totalMinutes * 60; return i18nc("contains abbreviated time units: (m)inutes and (s)econds", "%1m %2s ago", totalMinutes, seconds); } if (!totalDays) { const int seconds = totalSeconds - totalMinutes * 60; const int minutes = totalMinutes - totalHours * 60; return i18nc("contains abbreviated time units: (h)ours, (m)inutes and (s)econds)", "%1h %2m %3s ago", totalHours, minutes, seconds); } const int minutes = totalMinutes - totalHours * 60; const int hours = totalHours - totalDays * 24; return i18ncp("contains also abbreviated time units: (h)ours and (m)inutes", "%1 day %2h %3m ago", "%1 days %2h %3m ago", totalDays, hours, minutes); } qreal Formatter::scaleDownFactor(const QVariant &value, Unit unit, MetricPrefix targetPrefix) { const Unit adjusted = adjustedUnit(value.toDouble(), unit, targetPrefix); if (adjusted == unit) { return 1; } return std::pow(unitOrder(unit), adjusted - unit); } KLocalizedString Formatter::localizedString(const QVariant &value, Unit unit, MetricPrefix targetPrefix) { const Unit adjusted = adjustedUnit(value.toDouble(), unit, targetPrefix); return unitFormat(adjusted); } QString Formatter::formatValue(const QVariant &value, Unit unit, MetricPrefix targetPrefix, FormatOptions options) { switch (unit) { case UnitByte: case UnitKiloByte: case UnitMegaByte: case UnitGigaByte: case UnitTeraByte: case UnitPetaByte: case UnitByteRate: case UnitKiloByteRate: case UnitMegaByteRate: case UnitGigaByteRate: case UnitTeraByteRate: case UnitPetaByteRate: case UnitHertz: case UnitKiloHertz: case UnitMegaHertz: case UnitGigaHertz: case UnitTeraHertz: case UnitPetaHertz: case UnitPercent: case UnitRate: case UnitRpm: case UnitCelsius: case UnitDecibelMilliWatts: case UnitVolt: case UnitWatt: case UnitSecond: return formatNumber(value, unit, targetPrefix, options); case UnitBootTimestamp: return formatBootTimestamp(value, options); case UnitTime: return formatTime(value); default: return value.toString(); } } QString Formatter::symbol(Unit unit) { // TODO: Is it possible to avoid duplication of these symbols? switch (unit) { case UnitByte: return i18nc("Bytes unit symbol", "B"); case UnitKiloByte: return i18nc("Kilobytes unit symbol", "KiB"); case UnitMegaByte: return i18nc("Megabytes unit symbol", "MiB"); case UnitGigaByte: return i18nc("Gigabytes unit symbol", "GiB"); case UnitTeraByte: return i18nc("Terabytes unit symbol", "TiB"); case UnitPetaByte: return i18nc("Petabytes unit symbol", "PiB"); case UnitByteRate: return i18nc("Bytes per second unit symbol", "B/s"); case UnitKiloByteRate: return i18nc("Kilobytes per second unit symbol", "KiB/s"); case UnitMegaByteRate: return i18nc("Megabytes per second unit symbol", "MiB/s"); case UnitGigaByteRate: return i18nc("Gigabytes per second unit symbol", "GiB/s"); case UnitTeraByteRate: return i18nc("Gigabytes per second unit symbol", "TiB/s"); case UnitPetaByteRate: return i18nc("Gigabytes per second unit symbol", "PiB/s"); case UnitHertz: return i18nc("Hertz unit symbol", "Hz"); case UnitKiloHertz: return i18nc("Kilohertz unit symbol", "kHz"); case UnitMegaHertz: return i18nc("Megahertz unit symbol", "MHz"); case UnitGigaHertz: return i18nc("Gigahertz unit symbol", "GHz"); case UnitTeraHertz: return i18nc("Terahertz unit symbol", "THz"); case UnitPetaHertz: return i18nc("Petahertz unit symbol", "PHz"); case UnitPercent: return i18nc("Percent unit", "%"); case UnitRpm: return i18nc("Revolutions per minute unit symbol", "RPM"); case UnitCelsius: return i18nc("Celsius unit symbol", "°C"); case UnitDecibelMilliWatts: return i18nc("Decibels unit symbol", "dBm"); case UnitSecond: return i18nc("Seconds unit symbol", "s"); case UnitVolt: return i18nc("Volts unit symbol", "V"); case UnitWatt: return i18nc("Watts unit symbol", "W"); case UnitRate: return i18nc("Rate unit symbol", "s⁻¹"); default: return QString(); } } } // namespace KSysGuard diff --git a/formatter/Formatter.h b/formatter/Formatter.h index 268bc7c..25b1dd8 100644 --- a/formatter/Formatter.h +++ b/formatter/Formatter.h @@ -1,92 +1,92 @@ /* - Copyright (C) 2019 Vlad Zagorodniy + Copyright (C) 2019 Vlad Zahorodnii This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #pragma once // Own #include "formatter_export.h" #include "Unit.h" // Qt #include #include class KLocalizedString; namespace KSysGuard { /** * This enum type is used to specify format options. */ enum FormatOption { FormatOptionNone = 0, FormatOptionAgo = 1 << 0, FormatOptionShowNull = 1 << 1, }; Q_DECLARE_FLAGS(FormatOptions, FormatOption) class FORMATTER_EXPORT Formatter { public: /** * Returns the scale factor suitable for display. * * @param value The maximum output value. * @param unit The unit of the value. * @param targetPrefix Preferred metric prefix. */ static qreal scaleDownFactor(const QVariant &value, Unit unit, MetricPrefix targetPrefix = MetricPrefixAutoAdjust); /** * Returns localized string that is suitable for display. * * @param value The maximum output value. * @param unit The unit of the value. * @param targetPrefix Preferred metric prefix. */ static KLocalizedString localizedString(const QVariant &value, Unit unit, MetricPrefix targetPrefix = MetricPrefixAutoAdjust); /** * Converts @p value to the appropriate displayable string. * * The returned string is localized. * * @param value The value to be converted. * @param unit The unit of the value. * @param targetPrefix Preferred metric prefix. * @param options */ static QString formatValue(const QVariant &value, Unit unit, MetricPrefix targetPrefix = MetricPrefixAutoAdjust, FormatOptions options = FormatOptionNone); /** * Returns a symbol that corresponds to the given @p unit. * * The returned unit symbol is localized. */ static QString symbol(Unit unit); }; } // namespace KSysGuard Q_DECLARE_OPERATORS_FOR_FLAGS(KSysGuard::FormatOptions) diff --git a/formatter/Unit.h b/formatter/Unit.h index 7835ae3..900c9f0 100644 --- a/formatter/Unit.h +++ b/formatter/Unit.h @@ -1,93 +1,93 @@ /* - Copyright (C) 2019 Vlad Zagorodniy + Copyright (C) 2019 Vlad Zahorodnii This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #pragma once // Qt #include #include "formatter_export.h" namespace KSysGuard { FORMATTER_EXPORT Q_NAMESPACE /** * This enum type is used to specify metric prefixes. */ enum MetricPrefix { MetricPrefixAutoAdjust = -1, MetricPrefixUnity = 0, MetricPrefixKilo, MetricPrefixMega, MetricPrefixGiga, MetricPrefixTera, MetricPrefixPeta, MetricPrefixLast = MetricPrefixPeta }; Q_ENUM_NS(MetricPrefix) /** * This enum types is used to specify units. */ enum Unit { UnitInvalid = -1, UnitNone = 0, // Byte size units. UnitByte = 100, UnitKiloByte = MetricPrefixKilo + UnitByte, UnitMegaByte = MetricPrefixMega + UnitByte, UnitGigaByte = MetricPrefixGiga + UnitByte, UnitTeraByte = MetricPrefixTera + UnitByte, UnitPetaByte = MetricPrefixPeta + UnitByte, // Data rate units. UnitByteRate = 200, UnitKiloByteRate = MetricPrefixKilo + UnitByteRate, UnitMegaByteRate = MetricPrefixMega + UnitByteRate, UnitGigaByteRate = MetricPrefixGiga + UnitByteRate, UnitTeraByteRate = MetricPrefixTera + UnitByteRate, UnitPetaByteRate = MetricPrefixPeta + UnitByteRate, // Frequency. UnitHertz = 300, UnitKiloHertz = MetricPrefixKilo + UnitHertz, UnitMegaHertz = MetricPrefixMega + UnitHertz, UnitGigaHertz = MetricPrefixGiga + UnitHertz, UnitTeraHertz = MetricPrefixTera + UnitHertz, UnitPetaHertz = MetricPrefixPeta + UnitHertz, // Time units. UnitBootTimestamp = 400, UnitSecond, UnitTime, // Misc units. UnitCelsius = 500, UnitDecibelMilliWatts, UnitPercent, UnitRate, UnitRpm, UnitVolt, UnitWatt, }; Q_ENUM_NS(Unit) } // namespace KSysGuard diff --git a/processcore/formatter.h b/processcore/formatter.h index 32af39d..9ff89f8 100644 --- a/processcore/formatter.h +++ b/processcore/formatter.h @@ -1,20 +1,20 @@ /* - Copyright (C) 2019 Vlad Zagorodniy + Copyright (C) 2019 Vlad Zahorodnii This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "formatter/Formatter.h" diff --git a/processcore/unit.h b/processcore/unit.h index 4fce88d..1551555 100644 --- a/processcore/unit.h +++ b/processcore/unit.h @@ -1,20 +1,20 @@ /* - Copyright (C) 2019 Vlad Zagorodniy + Copyright (C) 2019 Vlad Zahorodnii This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "formatter/Unit.h" diff --git a/sensors/CMakeLists.txt b/sensors/CMakeLists.txt new file mode 100644 index 0000000..52c4e87 --- /dev/null +++ b/sensors/CMakeLists.txt @@ -0,0 +1,65 @@ +set(KSYSGUARD_SENSORS_SOVERSION 1) + +add_subdirectory(declarative) + +add_definitions(-DTRANSLATION_DOMAIN=\"ksysguard_sensors\") + +set(sensors_LIB_SRCS + Sensor.cpp + SensorDataModel.cpp + SensorTreeModel.cpp + SensorQuery.cpp + SensorDaemonInterface.cpp +) + +set(sensors_LIB_HEADERS + Sensor.h + SensorDataModel.h + SensorTreeModel.h + SensorQuery.h + SensorInfo_p.h +) + +ecm_qt_declare_logging_category(sensors_LIB_SRCS + HEADER sensors_logging.h + IDENTIFIER LIBKSYSGUARD_SENSORS + CATEGORY_NAME org.kde.libksysguard.sensors +) + +set_source_files_properties(org.kde.KSysGuardDaemon.xml PROPERTIES INCLUDE SensorInfo_p.h) +qt5_add_dbus_interface(sensors_LIB_SRCS org.kde.KSysGuardDaemon.xml ksysguarddaemon) + +add_library(Sensors ${sensors_LIB_SRCS}) +add_library(KSysGuard::Sensors ALIAS Sensors) + +target_include_directories(Sensors + PUBLIC + "$" + "$" +) + +generate_export_header(Sensors) + +target_link_libraries(Sensors + PUBLIC + Qt5::Qml + KSysGuard::Formatter + PRIVATE + Qt5::Core + Qt5::DBus + KF5::I18n +) + +set_target_properties(Sensors PROPERTIES + LIBRARY_OUTPUT_NAME KSysGuardSensors + VERSION ${KSYSGUARD_VERSION_STRING} + SOVERSION ${KSYSGUARD_SENSORS_SOVERSION} +) + +install(TARGETS Sensors EXPORT libksysguardLibraryTargets ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) +install(FILES + ${sensors_LIB_HEADERS} + ${CMAKE_CURRENT_BINARY_DIR}/sensors_export.h + DESTINATION ${KDE_INSTALL_INCLUDEDIR}/ksysguard/sensors + COMPONENT Devel +) diff --git a/sensors/Sensor.cpp b/sensors/Sensor.cpp new file mode 100644 index 0000000..6730a4f --- /dev/null +++ b/sensors/Sensor.cpp @@ -0,0 +1,259 @@ +/* + Copyright (C) 2019 Vlad Zahorodnii + Copyright (C) 2020 Arjen Hiemstra + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include + +#include "Sensor.h" +#include "SensorDaemonInterface_p.h" +#include "SensorInfo_p.h" +#include "formatter/Formatter.h" +#include "SensorQuery.h" + +using namespace KSysGuard; + +class Q_DECL_HIDDEN Sensor::Private +{ +public: + SensorInfo sensorInfo; + + Sensor::Status status = Sensor::Status::Unknown; + QVariant value; + + bool usedByQml = false; + bool componentComplete = false; + + QString pendingId; + QString id; + + bool enabled = true; +}; + +Sensor::Sensor(QObject *parent) + : Sensor(QString{}, parent) +{ +} + +Sensor::Sensor(const QString &id, QObject *parent) + : QObject(parent) + , d(new Private()) +{ + connect(this, &Sensor::statusChanged, this, &Sensor::valueChanged); + connect(this, &Sensor::statusChanged, this, &Sensor::metaDataChanged); + connect(this, &Sensor::enabledChanged, this, &Sensor::onEnabledChanged); + + setSensorId(id); +} + +Sensor::Sensor(const SensorQuery &query, int index, QObject *parent) + : Sensor(QString{}, parent) +{ + if (index > 0 && index < query.result().size()) { + auto result = query.result().at(index); + d->id = result.first; + onMetaDataChanged(d->id, result.second); + onEnabledChanged(); + } +} + +bool Sensor::event(QEvent *event) +{ + if (event->type() == QEvent::ParentAboutToChange && parent()) { + parent()->disconnect(this); + } else if (event->type() == QEvent::ParentChange && parent()) { + if (parent()->metaObject()->indexOfSignal("enabledChanged()") != -1) { + connect(parent(), SIGNAL(enabledChanged()), this, SIGNAL(enabledChanged())); + } + } + + return QObject::event(event); +} + +Sensor::~Sensor() +{ + SensorDaemonInterface::instance()->unsubscribe(d->id); +} + +QString Sensor::sensorId() const +{ + return d->id; +} + +void Sensor::setSensorId(const QString &id) +{ + if (id == d->id) { + return; + } + + if (d->usedByQml && !d->componentComplete) { + d->pendingId = id; + return; + } + + d->id = id; + d->status = Sensor::Status::Loading; + + if (!id.isEmpty()) { + SensorDaemonInterface::instance()->requestMetaData(id); + connect(SensorDaemonInterface::instance(), &SensorDaemonInterface::metaDataChanged, this, &Sensor::onMetaDataChanged, Qt::UniqueConnection); + } + + if (enabled()) { + SensorDaemonInterface::instance()->subscribe(id); + SensorDaemonInterface::instance()->requestValue(id); + connect(SensorDaemonInterface::instance(), &SensorDaemonInterface::valueChanged, this, &Sensor::onValueChanged, Qt::UniqueConnection); + } + + Q_EMIT sensorIdChanged(); + Q_EMIT statusChanged(); +} + +Sensor::Status Sensor::status() const +{ + return d->status; +} + +QString Sensor::name() const +{ + return d->sensorInfo.name; +} + +QString Sensor::shortName() const +{ + if (d->sensorInfo.shortName.isEmpty()) { + return d->sensorInfo.name; + } + + return d->sensorInfo.name; +} + +QString Sensor::description() const +{ + return d->sensorInfo.description; +} + +Unit Sensor::unit() const +{ + return d->sensorInfo.unit; +} + +qreal Sensor::minimum() const +{ + return d->sensorInfo.min; +} + +qreal Sensor::maximum() const +{ + return d->sensorInfo.max; +} + +QVariant::Type Sensor::type() const +{ + return d->sensorInfo.variantType; +} + +QVariant Sensor::value() const +{ + if (!d->value.isValid()) { + return QVariant{d->sensorInfo.variantType}; + } + return d->value; +} + +QString Sensor::formattedValue() const +{ + return Formatter::formatValue(value(), unit(), MetricPrefixAutoAdjust, FormatOptionShowNull); +} + +bool Sensor::enabled() const +{ + if (d->enabled && parent()) { + auto parentEnabled = parent()->property("enabled"); + if (parentEnabled.isValid()) { + return parentEnabled.toBool(); + } + } + + return d->enabled; +} + +void Sensor::setEnabled(bool newEnabled) +{ + if (newEnabled == d->enabled) { + return; + } + + d->enabled = newEnabled; + Q_EMIT enabledChanged(); +} + +void Sensor::classBegin() +{ + d->usedByQml = true; +} + +void Sensor::componentComplete() +{ + d->componentComplete = true; + + setSensorId(d->pendingId); + + if (parent() && parent()->metaObject()->indexOfSignal("enabledChanged()") != -1) { + connect(parent(), SIGNAL(enabledChanged()), this, SIGNAL(enabledChanged())); + } +} + +void Sensor::onMetaDataChanged(const QString &sensorId, const SensorInfo &metaData) +{ + if (sensorId != d->id || !enabled()) { + return; + } + + d->sensorInfo = metaData; + + if (d->status == Sensor::Status::Loading) { + d->status = Sensor::Status::Ready; + Q_EMIT statusChanged(); + } + + Q_EMIT metaDataChanged(); +} + +void Sensor::onValueChanged(const QString &sensorId, const QVariant &value) +{ + if (sensorId != d->id || !enabled()) { + return; + } + + d->value = value; + Q_EMIT valueChanged(); +} + +void Sensor::onEnabledChanged() +{ + if (enabled()) { + SensorDaemonInterface::instance()->subscribe(d->id); + // Force an update of metadata and data, since that may have changed + // while we were disabled. + SensorDaemonInterface::instance()->requestMetaData(d->id); + SensorDaemonInterface::instance()->requestValue(d->id); + } else { + SensorDaemonInterface::instance()->unsubscribe(d->id); + } +} diff --git a/sensors/Sensor.h b/sensors/Sensor.h new file mode 100644 index 0000000..48f30ad --- /dev/null +++ b/sensors/Sensor.h @@ -0,0 +1,188 @@ +/* + Copyright (C) 2019 Vlad Zahorodnii + Copyright (C) 2020 Arjen Hiemstra + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#pragma once + +#include + +#include +#include +#include +#include + +#include "formatter/Unit.h" + +#include "sensors_export.h" + +namespace KSysGuard +{ +class SensorData; +class SensorInfo; +class SensorQuery; + +/** + * An object encapsulating a backend sensor. + * + * This class represents a sensor as exposed by the backend. It allows querying + * various metadata properties of the sensor as well as the current value. + */ +class SENSORS_EXPORT Sensor : public QObject, public QQmlParserStatus +{ + Q_OBJECT + Q_INTERFACES(QQmlParserStatus) + + /** + * The path to the backend sensor this Sensor represents. + */ + Q_PROPERTY(QString sensorId READ sensorId WRITE setSensorId NOTIFY sensorIdChanged) + /** + * The user-visible name of this Sensor. + */ + Q_PROPERTY(QString name READ name NOTIFY metaDataChanged) + /** + * A shortened name that can be displayed when space is constrained. + * + * The value is the same as name if shortName was not provided by the backend. + */ + Q_PROPERTY(QString shortName READ shortName NOTIFY metaDataChanged) + /** + * A description of the Sensor. + */ + Q_PROPERTY(QString description READ description NOTIFY metaDataChanged) + /** + * The unit of this Sensor. + */ + Q_PROPERTY(KSysGuard::Unit unit READ unit NOTIFY metaDataChanged) + /** + * The minimum value this Sensor can have. + */ + Q_PROPERTY(qreal minimum READ minimum NOTIFY metaDataChanged) + /** + * The maximum value this Sensor can have. + */ + Q_PROPERTY(qreal maximum READ maximum NOTIFY metaDataChanged) + /** + * The QVariant type for this sensor. + * + * This is used to create proper default values. + */ + Q_PROPERTY(QVariant::Type type READ type NOTIFY metaDataChanged) + /** + * The status of the sensor. + * + * Due to the asynchronous nature of the underlying code, sensors are not + * immediately available on construction. Instead, they need to request data + * from the daemon and wait for it to arrive. This property reflects where + * in that process this sensor is. + */ + Q_PROPERTY(Status status READ status NOTIFY statusChanged) + /** + * The current value of this sensor. + */ + Q_PROPERTY(QVariant value READ value NOTIFY valueChanged) + /** + * A formatted version of \property value. + */ + Q_PROPERTY(QString formattedValue READ formattedValue NOTIFY valueChanged) + /** + * Should this Sensor check for changes? + * + * Note that if set to true, the sensor will only be enabled when the parent + * is also enabled. + */ + Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged) + +public: + /** + * This enum type is used to specify status of the Sensor. + */ + enum class Status { + Unknown, ///< The sensor has no ID assigned. + Loading, ///< The sensor is currently being loaded. + Ready, ///< The sensor has been loaded. + Error, ///< An error occurred or the sensor has been removed. + Removed, ///< Removed from backend + }; + Q_ENUM(Status) + + explicit Sensor(QObject *parent = nullptr); + explicit Sensor(const QString &id, QObject *parent = nullptr); + /** + * Construct a Sensor from a SensorQuery result and index. + * + * This avoids an extra lookup for the sensor metadata. + */ + Sensor(const SensorQuery &query, int index, QObject *parent = nullptr); + ~Sensor() override; + + bool event(QEvent *event) override; + + QString sensorId() const; + void setSensorId(const QString &id); + Q_SIGNAL void sensorIdChanged() const; + + Status status() const; + Q_SIGNAL void statusChanged() const; + + QString name() const; + QString shortName() const; + QString description() const; + KSysGuard::Unit unit() const; + qreal minimum() const; + qreal maximum() const; + QVariant::Type type() const; + /** + * This signal is emitted when any of the metadata properties change. + */ + Q_SIGNAL void metaDataChanged() const; + + /** + * Returns the output of the sensor. + * + * The returned value is the most recent sensor data received from the ksysguard + * daemon, it's not necessarily the actual current output value. + * + * The client can't control how often the sensor data is sampled. The ksysguard + * daemon is in charge of picking the sample rate. When the Sensor receives new + * output value, dataChanged signal will be emitted. + * + * @see dataChanged + */ + QVariant value() const; + QString formattedValue() const; + Q_SIGNAL void valueChanged() const; + + bool enabled() const; + void setEnabled(bool newEnabled); + Q_SIGNAL void enabledChanged(); + + void classBegin() override; + void componentComplete() override; + +private: + void onMetaDataChanged(const QString &sensorId, const SensorInfo &metaData); + void onValueChanged(const QString &sensorId, const QVariant &value); + void onEnabledChanged(); + + class Private; + const std::unique_ptr d; +}; + +} // namespace KSysGuard diff --git a/sensors/SensorDaemonInterface.cpp b/sensors/SensorDaemonInterface.cpp new file mode 100644 index 0000000..f79b6f4 --- /dev/null +++ b/sensors/SensorDaemonInterface.cpp @@ -0,0 +1,145 @@ +/* + Copyright (C) 2020 Arjen Hiemstra + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "SensorDaemonInterface_p.h" + +#include + +#include "ksysguarddaemon.h" + +using namespace KSysGuard; + +class SensorDaemonInterface::Private +{ +public: + std::unique_ptr dbusInterface; + + static const QString SensorServiceName; + static const QString SensorPath; +}; + +const QString SensorDaemonInterface::Private::SensorServiceName = QStringLiteral("org.kde.kstats"); +const QString SensorDaemonInterface::Private::SensorPath = QStringLiteral("/"); + +SensorDaemonInterface::SensorDaemonInterface(QObject *parent) + : QObject(parent) + , d(new Private) +{ + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + + d->dbusInterface = std::make_unique(Private::SensorServiceName, Private::SensorPath, QDBusConnection::sessionBus()); + + connect(d->dbusInterface.get(), &org::kde::KSysGuardDaemon::sensorMetaDataChanged, this, &SensorDaemonInterface::onMetaDataChanged); + connect(d->dbusInterface.get(), &org::kde::KSysGuardDaemon::newSensorData, this, &SensorDaemonInterface::onValueChanged); + connect(d->dbusInterface.get(), &org::kde::KSysGuardDaemon::sensorAdded, this, &SensorDaemonInterface::sensorAdded); + connect(d->dbusInterface.get(), &org::kde::KSysGuardDaemon::sensorRemoved, this, &SensorDaemonInterface::sensorRemoved); +} + +SensorDaemonInterface::~SensorDaemonInterface() +{ +} + +void SensorDaemonInterface::requestMetaData(const QString &sensorId) +{ + requestMetaData(QStringList{sensorId}); +} + +void SensorDaemonInterface::requestMetaData(const QStringList &sensorIds) +{ + auto watcher = new QDBusPendingCallWatcher{d->dbusInterface->sensors(sensorIds), this}; + connect(watcher, &QDBusPendingCallWatcher::finished, watcher, [this](QDBusPendingCallWatcher *self) { + self->deleteLater(); + + const QDBusPendingReply reply = *self; + if (reply.isError()) { + return; + } + + const auto infos = reply.value(); + for (auto itr = infos.begin(); itr != infos.end(); ++itr) { + Q_EMIT metaDataChanged(itr.key(), itr.value()); + } + }); +} + +void SensorDaemonInterface::requestValue(const QString &sensorId) +{ + auto watcher = new QDBusPendingCallWatcher{d->dbusInterface->sensorData({sensorId}), this}; + connect(watcher, &QDBusPendingCallWatcher::finished, watcher, [this](QDBusPendingCallWatcher *self) { + self->deleteLater(); + + const QDBusPendingReply reply = *self; + if (reply.isError()) { + return; + } + + const auto allData = reply.value(); + for (auto data : allData) { + Q_EMIT valueChanged(data.attribute, data.payload); + } + }); +} + +QDBusPendingCallWatcher *SensorDaemonInterface::allSensors() const +{ + return new QDBusPendingCallWatcher{d->dbusInterface->allSensors()}; +} + +void SensorDaemonInterface::subscribe(const QString &sensorId) +{ + subscribe(QStringList{sensorId}); +} + +void KSysGuard::SensorDaemonInterface::subscribe(const QStringList &sensorIds) +{ + d->dbusInterface->subscribe(sensorIds); +} + +void SensorDaemonInterface::unsubscribe(const QString &sensorId) +{ + unsubscribe(QStringList{sensorId}); +} + +void KSysGuard::SensorDaemonInterface::unsubscribe(const QStringList &sensorIds) +{ + d->dbusInterface->unsubscribe(sensorIds); +} + +SensorDaemonInterface *SensorDaemonInterface::instance() +{ + static SensorDaemonInterface instance; + return &instance; +} + +void SensorDaemonInterface::onMetaDataChanged(const QHash &metaData) +{ + for (auto itr = metaData.begin(); itr != metaData.end(); ++itr) { + Q_EMIT metaDataChanged(itr.key(), itr.value()); + } +} + +void SensorDaemonInterface::onValueChanged(const SensorDataList &values) +{ + for (auto entry : values) { + Q_EMIT valueChanged(entry.attribute, entry.payload); + } +} diff --git a/sensors/SensorDaemonInterface_p.h b/sensors/SensorDaemonInterface_p.h new file mode 100644 index 0000000..9f6d3aa --- /dev/null +++ b/sensors/SensorDaemonInterface_p.h @@ -0,0 +1,70 @@ +/* + Copyright (C) 2020 Arjen Hiemstra + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#pragma once + +#include "SensorInfo_p.h" +#include +#include + +class QDBusPendingCallWatcher; + +namespace KSysGuard +{ +/** + * Internal helper class to communicate with the daemon. + * + * This is mostly for convenience on top of the auto-generated KSysGuardDaemon + * D-Bus interface. + */ +class SensorDaemonInterface : public QObject +{ + Q_OBJECT + +public: + SensorDaemonInterface(QObject *parent = nullptr); + ~SensorDaemonInterface() override; + + void requestMetaData(const QString &sensorId); + void requestMetaData(const QStringList &sensorIds); + Q_SIGNAL void metaDataChanged(const QString &sensorId, const SensorInfo &info); + void requestValue(const QString &sensorId); + Q_SIGNAL void valueChanged(const QString &sensorId, const QVariant &value); + + QDBusPendingCallWatcher *allSensors() const; + + void subscribe(const QString &sensorId); + void subscribe(const QStringList &sensorIds); + void unsubscribe(const QString &sensorId); + void unsubscribe(const QStringList &sensorIds); + + Q_SIGNAL void sensorAdded(const QString &sensorId); + Q_SIGNAL void sensorRemoved(const QString &sensorId); + + static SensorDaemonInterface *instance(); + +private: + void onMetaDataChanged(const QHash &metaData); + void onValueChanged(const SensorDataList &values); + + class Private; + const std::unique_ptr d; +}; + +} diff --git a/sensors/SensorDataModel.cpp b/sensors/SensorDataModel.cpp new file mode 100644 index 0000000..89ceaa6 --- /dev/null +++ b/sensors/SensorDataModel.cpp @@ -0,0 +1,363 @@ +/* + Copyright (c) 2019 Eike Hein + Copyright (C) 2020 Arjen Hiemstra + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include + +#include "Formatter.h" +#include "SensorDaemonInterface_p.h" +#include "SensorDataModel.h" +#include "SensorInfo_p.h" +#include "sensors_logging.h" + +using namespace KSysGuard; + +class Q_DECL_HIDDEN SensorDataModel::Private +{ +public: + Private(SensorDataModel *qq) + : q(qq) + { + } + + void sensorsChanged(); + void addSensor(const QString &id); + void removeSensor(const QString &id); + + QStringList requestedSensors; + + QStringList sensors; + QStringList objects; + + QHash sensorInfos; + QHash sensorData; + + bool usedByQml = false; + bool componentComplete = false; + bool loaded = false; + +private: + SensorDataModel *q; +}; + +SensorDataModel::SensorDataModel(const QStringList &sensorIds, QObject *parent) + : QAbstractTableModel(parent) + , d(new Private(this)) +{ + connect(SensorDaemonInterface::instance(), &SensorDaemonInterface::sensorAdded, this, &SensorDataModel::onSensorAdded); + connect(SensorDaemonInterface::instance(), &SensorDaemonInterface::sensorRemoved, this, &SensorDataModel::onSensorRemoved); + connect(SensorDaemonInterface::instance(), &SensorDaemonInterface::metaDataChanged, this, &SensorDataModel::onMetaDataChanged); + connect(SensorDaemonInterface::instance(), &SensorDaemonInterface::valueChanged, this, &SensorDataModel::onValueChanged); + + // Empty string is used for entries that do not specify a wildcard object + d->objects << QStringLiteral(""); + + setSensors(sensorIds); +} + +SensorDataModel::~SensorDataModel() +{ +} + +QHash SensorDataModel::roleNames() const +{ + QHash roles = QAbstractItemModel::roleNames(); + + QMetaEnum e = metaObject()->enumerator(metaObject()->indexOfEnumerator("AdditionalRoles")); + + for (int i = 0; i < e.keyCount(); ++i) { + roles.insert(e.value(i), e.key(i)); + } + + return roles; +} + +QVariant SensorDataModel::data(const QModelIndex &index, int role) const +{ + const bool check = checkIndex(index, CheckIndexOption::IndexIsValid | CheckIndexOption::DoNotUseParent); + if (!check) { + return QVariant(); + } + + auto sensor = d->sensors.at(index.column()); + auto info = d->sensorInfos.value(sensor); + auto data = d->sensorData.value(sensor); + + switch (role) { + case Qt::DisplayRole: + case FormattedValue: + return Formatter::formatValue(data, info.unit); + case Value: + return data; + case Unit: + return info.unit; + case Name: + return info.name; + case ShortName: + if (info.shortName.isEmpty()) { + return info.name; + } + return info.shortName; + case Description: + return info.description; + case Minimum: + return info.min; + case Maximum: + return info.max; + case Type: + return info.variantType; + case SensorId: + return sensor; + default: + break; + } + + return QVariant(); +} + +QVariant SensorDataModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation == Qt::Vertical) { + return QVariant(); + } + + if (section < 0 || section >= d->sensors.size()) { + return QVariant(); + } + + auto sensor = d->sensors.at(section); + auto info = d->sensorInfos.value(sensor); + + switch (role) { + case Qt::DisplayRole: + case ShortName: + if (info.shortName.isEmpty()) { + return info.name; + } + return info.shortName; + case Name: + return info.name; + case SensorId: + return sensor; + case Unit: + return info.unit; + case Description: + return info.description; + case Minimum: + return info.min; + case Maximum: + return info.max; + case Type: + return info.variantType; + default: + break; + } + + return QVariant(); +} + +int SensorDataModel::rowCount(const QModelIndex &parent) const +{ + if (parent.isValid()) { + return 0; + } + + return d->objects.count(); +} + +int SensorDataModel::columnCount(const QModelIndex &parent) const +{ + if (parent.isValid()) { + return 0; + } + + return d->sensors.count(); +} + +qreal SensorDataModel::minimum() const +{ + if (d->sensors.isEmpty()) { + return 0; + } + + auto result = std::min_element(d->sensorInfos.cbegin(), d->sensorInfos.cend(), [](const SensorInfo &first, const SensorInfo &second) { return first.min < second.min; }); + return (*result).min; +} + +qreal SensorDataModel::maximum() const +{ + if (d->sensors.isEmpty()) { + return 0; + } + + auto result = std::max_element(d->sensorInfos.cbegin(), d->sensorInfos.cend(), [](const SensorInfo &first, const SensorInfo &second) { return first.max < second.max; }); + return (*result).max; +} + +QStringList SensorDataModel::sensors() const +{ + return d->requestedSensors; +} + +void SensorDataModel::setSensors(const QStringList &sensorIds) +{ + if (d->requestedSensors == sensorIds) { + return; + } + + d->requestedSensors = sensorIds; + + if (!d->usedByQml || d->componentComplete) { + d->sensorsChanged(); + } + Q_EMIT sensorsChanged(); +} + +void SensorDataModel::addSensor(const QString &sensorId) +{ + d->addSensor(sensorId); +} + +void SensorDataModel::removeSensor(const QString &sensorId) +{ + d->removeSensor(sensorId); +} + +int KSysGuard::SensorDataModel::column(const QString &sensorId) const +{ + return d->sensors.indexOf(sensorId); +} + +void KSysGuard::SensorDataModel::classBegin() +{ + d->usedByQml = true; +} + +void KSysGuard::SensorDataModel::componentComplete() +{ + d->componentComplete = true; + + d->sensorsChanged(); + + emit sensorsChanged(); +} + +void SensorDataModel::Private::addSensor(const QString &id) +{ + if (requestedSensors.indexOf(id) != -1) { + return; + } + + qCDebug(LIBKSYSGUARD_SENSORS) << "Add Sensor" << id; + + sensors.append(id); + SensorDaemonInterface::instance()->subscribe(id); + SensorDaemonInterface::instance()->requestMetaData(id); +} + +void SensorDataModel::Private::removeSensor(const QString &id) +{ + const int col = sensors.indexOf(id); + if (col == -1) { + return; + } + + q->beginRemoveColumns(QModelIndex(), col, col); + + sensors.removeAt(col); + sensorInfos.remove(id); + sensorData.remove(id); + + q->endRemoveColumns(); +} + +void SensorDataModel::onSensorAdded(const QString &sensorId) +{ + d->addSensor(sensorId); +} + +void SensorDataModel::onSensorRemoved(const QString &sensorId) +{ + d->removeSensor(sensorId); +} + +void SensorDataModel::onMetaDataChanged(const QString &sensorId, const SensorInfo &info) +{ + auto column = d->sensors.indexOf(sensorId); + if (column == -1) { + return; + } + + qCDebug(LIBKSYSGUARD_SENSORS) << "Received metadata change for" << sensorId; + + // Simple case: Just an update for a sensor's metadata + if (d->sensorInfos.contains(sensorId)) { + d->sensorInfos[sensorId] = info; + Q_EMIT dataChanged(index(0, column), index(0, column), {Qt::DisplayRole, Name, ShortName, Description, Unit, Minimum, Maximum, Type, FormattedValue}); + return; + } + + // Otherwise, it's a new sensor that was added + beginInsertColumns(QModelIndex{}, column, column); + d->sensorInfos[sensorId] = info; + d->sensorData[sensorId] = QVariant{}; + endInsertColumns(); + + SensorDaemonInterface::instance()->requestValue(sensorId); + emit sensorMetaDataChanged(); +} + +void SensorDataModel::onValueChanged(const QString &sensorId, const QVariant &value) +{ + if (!d->sensorData.contains(sensorId)) { + return; + } + + auto column = d->sensors.indexOf(sensorId); + d->sensorData[sensorId] = value; + Q_EMIT dataChanged(index(0, column), index(0, column), {Qt::DisplayRole, Value, FormattedValue}); +} + +void SensorDataModel::Private::sensorsChanged() +{ +#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) + auto newSet = QSet{requestedSensors.begin(), requestedSensors.end()}; + auto currentSet = QSet{sensors.begin(), sensors.end()}; +#else + auto newSet = requestedSensors.toSet(); + auto currentSet = sensors.toSet(); +#endif + + const auto addedSensors = newSet - currentSet; + const auto removedSensors = currentSet - newSet; + + sensors.append(addedSensors.values()); + + SensorDaemonInterface::instance()->subscribe(addedSensors.values()); + SensorDaemonInterface::instance()->requestMetaData(addedSensors.values()); + + bool itemsRemoved = false; + for (auto sensor : removedSensors) { + removeSensor(sensor); + itemsRemoved = true; + } + if (itemsRemoved) { + emit q->sensorMetaDataChanged(); + } +} diff --git a/sensors/SensorDataModel.h b/sensors/SensorDataModel.h new file mode 100644 index 0000000..a185bf2 --- /dev/null +++ b/sensors/SensorDataModel.h @@ -0,0 +1,109 @@ +/* + Copyright (c) 2019 Eike Hein + Copyright (C) 2020 Arjen Hiemstra + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#pragma once + +#include "sensors_export.h" +#include +#include +#include +#include + +namespace KSysGuard +{ +class SensorInfo; + +/** + * A model representing a table of sensors. + * + * This model will expose the metadata and values of a list of sensors as a + * table, using one column for each sensor. The metadata and values are + * represented as different roles. + */ +class SENSORS_EXPORT SensorDataModel : public QAbstractTableModel, public QQmlParserStatus +{ + Q_OBJECT + Q_INTERFACES(QQmlParserStatus) + + /** + * The list of sensors to watch. + */ + Q_PROPERTY(QStringList sensors READ sensors WRITE setSensors NOTIFY sensorsChanged) + /** + * The minimum value of all sensors' minimum property. + */ + Q_PROPERTY(qreal minimum READ minimum NOTIFY sensorMetaDataChanged) + /** + * The maximum value of all sensors' maximum property. + */ + Q_PROPERTY(qreal maximum READ maximum NOTIFY sensorMetaDataChanged) + +public: + enum AdditionalRoles { + SensorId = Qt::UserRole + 1, //< The backend path to the sensor. + Name, //< The name of the sensor. + ShortName, //< A shorter name for the sensor. This is equal to name if not set. + Description, //< A description for the sensor. + Unit, //< The unit of the sensor. + Minimum, //< The minimum value this sensor can have. + Maximum, //< The maximum value this sensor can have. + Type, //< The QVariant::Type of the sensor. + Value, //< The value of the sensor. + FormattedValue, //< A formatted string of the value of the sensor. + }; + Q_ENUM(AdditionalRoles) + + explicit SensorDataModel(const QStringList &sensorIds = {}, QObject *parent = nullptr); + virtual ~SensorDataModel(); + + QHash roleNames() const override; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + + QStringList sensors() const; + void setSensors(const QStringList &sensorIds); + Q_SIGNAL void sensorsChanged() const; + Q_SIGNAL void sensorMetaDataChanged(); + + qreal minimum() const; + qreal maximum() const; + + Q_INVOKABLE void addSensor(const QString &sensorId); + Q_INVOKABLE void removeSensor(const QString &sensorId); + Q_INVOKABLE int column(const QString &sensorId) const; + + void classBegin() override; + void componentComplete() override; + +private: + void onSensorAdded(const QString &sensorId); + void onSensorRemoved(const QString &sensorId); + void onMetaDataChanged(const QString &sensorId, const SensorInfo &info); + void onValueChanged(const QString &sensorId, const QVariant &value); + + class Private; + const std::unique_ptr d; +}; + +} // namespace KSysGuard diff --git a/sensors/SensorInfo_p.h b/sensors/SensorInfo_p.h new file mode 100644 index 0000000..f28a6c1 --- /dev/null +++ b/sensors/SensorInfo_p.h @@ -0,0 +1,114 @@ +/* + Copyright (c) 2019 David Edmundson + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ +#pragma once + +#include +#include +#include +#include +#include + +#include "formatter/Unit.h" + +namespace KSysGuard +{ +// Data that is static for the lifespan of the sensor +class SensorInfo +{ +public: + SensorInfo() = default; + QString name; // Translated name of the sensor. + QString shortName; // Shorter translated name of the sensor, to use when space is constrained. + QString description; // Translated description of the sensor. + QVariant::Type variantType = QVariant::Invalid; + KSysGuard::Unit unit = KSysGuard::UnitInvalid; // Both a format hint and implies data type (i.e double/string) + qreal min = 0; + qreal max = 0; +}; + +class Q_DECL_EXPORT SensorData +{ +public: + SensorData() = default; + SensorData(const QString &_attribute, const QVariant &_payload) + : attribute(_attribute) + , payload(_payload) + { + } + QString attribute; + QVariant payload; +}; + +typedef QHash SensorInfoMap; +typedef QList SensorDataList; + +inline QDBusArgument &operator<<(QDBusArgument &argument, const SensorInfo &s) +{ + argument.beginStructure(); + argument << s.name; + argument << s.shortName; + argument << s.description; + argument << s.variantType; + argument << s.unit; + argument << s.min; + argument << s.max; + argument.endStructure(); + return argument; +} + +inline const QDBusArgument &operator>>(const QDBusArgument &argument, SensorInfo &s) +{ + argument.beginStructure(); + argument >> s.name; + argument >> s.shortName; + argument >> s.description; + uint32_t t; + argument >> t; + s.variantType = static_cast(t); + argument >> t; + s.unit = static_cast(t); + argument >> s.min; + argument >> s.max; + argument.endStructure(); + return argument; +} + +inline QDBusArgument &operator<<(QDBusArgument &argument, const SensorData &s) +{ + argument.beginStructure(); + argument << s.attribute; + argument << QDBusVariant(s.payload); + argument.endStructure(); + return argument; +} + +inline const QDBusArgument &operator>>(const QDBusArgument &argument, SensorData &s) +{ + argument.beginStructure(); + argument >> s.attribute; + argument >> s.payload; + argument.endStructure(); + return argument; +} + +} // namespace KSysGuard + +Q_DECLARE_METATYPE(KSysGuard::SensorInfo); +Q_DECLARE_METATYPE(KSysGuard::SensorData); +Q_DECLARE_METATYPE(KSysGuard::SensorDataList); diff --git a/sensors/SensorQuery.cpp b/sensors/SensorQuery.cpp new file mode 100644 index 0000000..44f201d --- /dev/null +++ b/sensors/SensorQuery.cpp @@ -0,0 +1,135 @@ +/* + Copyright (C) 2020 Arjen Hiemstra + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "SensorQuery.h" + +#include +#include +#include + +#include "SensorDaemonInterface_p.h" +#include "sensors_logging.h" + +using namespace KSysGuard; + +class SensorQuery::Private +{ +public: + enum class State { Initial, Running, Finished }; + + void updateResult(const QDBusPendingReply &reply); + + QString path; + State state = State::Initial; + QVector> result; + + QDBusPendingCallWatcher *watcher = nullptr; +}; + +KSysGuard::SensorQuery::SensorQuery(const QString &path, QObject *parent) + : QObject(parent) + , d(std::make_unique()) +{ + d->path = path; +} + +KSysGuard::SensorQuery::~SensorQuery() +{ +} + +QString KSysGuard::SensorQuery::path() const +{ + return d->path; +} + +void KSysGuard::SensorQuery::setPath(const QString &path) +{ + if (path == d->path) { + return; + } + + if (d->state != Private::State::Initial) { + qCWarning(LIBKSYSGUARD_SENSORS) << "Cannot modify a running or finished query"; + return; + } + + d->path = path; +} + +bool KSysGuard::SensorQuery::execute() +{ + if (d->state != Private::State::Initial) { + return false; + } + + d->state = Private::State::Running; + + auto watcher = SensorDaemonInterface::instance()->allSensors(); + d->watcher = watcher; + connect(watcher, &QDBusPendingCallWatcher::finished, this, [watcher, this]() { + watcher->deleteLater(); + d->watcher = nullptr; + d->state = Private::State::Finished; + d->updateResult(QDBusPendingReply(*watcher)); + Q_EMIT finished(this); + }); + + return true; +} + +bool KSysGuard::SensorQuery::waitForFinished() +{ + if (!d->watcher) { + return false; + } + + d->watcher->waitForFinished(); + return true; +} + +QStringList KSysGuard::SensorQuery::sensorIds() const +{ + QStringList ids; + std::transform(d->result.cbegin(), d->result.cend(), std::back_inserter(ids), [](auto entry) { return entry.first; }); + return ids; +} + +QVector> KSysGuard::SensorQuery::result() const +{ + return d->result; +} + +void KSysGuard::SensorQuery::Private::updateResult(const QDBusPendingReply &reply) +{ + if (path.isEmpty()) { // add everything + const SensorInfoMap response = reply.value(); + for (auto it = response.constBegin(); it != response.constEnd(); it++) { + result.append(qMakePair(it.key(), it.value())); + } + return; + } + auto regexp = QRegularExpression{QRegularExpression::wildcardToRegularExpression(path)}; + + const auto sensorIds = reply.value().keys(); + for (auto id : sensorIds) { + if (regexp.match(id).hasMatch()) { + result.append(qMakePair(id, reply.value().value(id))); + } + } +} diff --git a/sensors/SensorQuery.h b/sensors/SensorQuery.h new file mode 100644 index 0000000..a3ead8f --- /dev/null +++ b/sensors/SensorQuery.h @@ -0,0 +1,77 @@ +/* + Copyright (C) 2020 Arjen Hiemstra + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#pragma once + +#include "sensors_export.h" +#include +#include + +namespace KSysGuard +{ +class SensorInfo; + +/** + * An object to query the daemon for a list of sensors and their metadata. + * + * This class will request a list of sensors from the daemon, then filter them + * based on the supplied path. The path can include the wildcard "*" to get a + * list of all sensors matching the specified part of their path. In addition, + * if left empty, all sensors will be returned. + */ +class SENSORS_EXPORT SensorQuery : public QObject +{ + Q_OBJECT + +public: + SensorQuery(const QString &path = QString{}, QObject *parent = nullptr); + ~SensorQuery() override; + + QString path() const; + void setPath(const QString &path); + + /** + * A list of sensors ids that match the query. + */ + QStringList sensorIds() const; + + /** + * Start processing the query. + */ + bool execute(); + /** + * Wait for the query to finish. + * + * Mostly useful for code that needs the result to be available before + * continuing. Ideally the finished() signal should be used instead. + */ + bool waitForFinished(); + + Q_SIGNAL void finished(const SensorQuery *query); + +private: + friend class Sensor; + friend class SensorTreeModel; + QVector> result() const; + + class Private; + const std::unique_ptr d; +}; + +} // namespace KSysGuard diff --git a/sensors/SensorTreeModel.cpp b/sensors/SensorTreeModel.cpp new file mode 100644 index 0000000..dd8e50c --- /dev/null +++ b/sensors/SensorTreeModel.cpp @@ -0,0 +1,445 @@ +/* + Copyright (c) 2019 Eike Hein + Copyright (C) 2020 Arjen Hiemstra + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "SensorTreeModel.h" + +#include +#include +#include +#include + +#include "formatter/Formatter.h" + +#include "Sensor.h" +#include "SensorDaemonInterface_p.h" +#include "SensorInfo_p.h" +#include "SensorQuery.h" + +using namespace KSysGuard; + +struct Q_DECL_HIDDEN SensorTreeItem +{ + SensorTreeItem *parent = nullptr; + QString name; + QVector children; + + inline int indexOf(const QString &name) + { + int index = -1; + + for (int i = 0; i < children.count(); ++i) { + if (children.at(i)->name == name) { + return i; + } + } + + return index; + } + + ~SensorTreeItem() + { + qDeleteAll(children); + } +}; + +class Q_DECL_HIDDEN SensorTreeModel::Private +{ +public: + Private(SensorTreeModel *qq) + : rootItem(new SensorTreeItem) + , q(qq) + { + } + ~Private() + { + delete rootItem; + } + + SensorTreeItem *rootItem; + QHash sensorInfos; + + void addSensor(const QString &sensorId, const SensorInfo &info); + void removeSensor(const QString &sensorId); + + QString sensorId(const QModelIndex &index); + + SensorTreeItem *find(const QString &sensorId); + +private: + SensorTreeModel *q; +}; + +void SensorTreeModel::Private::addSensor(const QString &sensorId, const SensorInfo &info) +{ + const QStringList &segments = sensorId.split(QLatin1Char('/')); + + if (!segments.count() || segments.at(0).isEmpty()) { + qDebug() << "Rejecting sensor" << sensorId << "- sensor id is not well-formed."; + return; + } + + SensorTreeItem *item = rootItem; + + for (auto segment : segments) { + int index = item->indexOf(segment); + + if (index != -1) { + item = item->children.at(index); + } else { + SensorTreeItem *newItem = new SensorTreeItem(); + newItem->parent = item; + newItem->name = segment; + + const QModelIndex &parentIndex = (item == rootItem) ? QModelIndex() : q->createIndex(item->parent->children.indexOf(item), 0, item); + q->beginInsertRows(parentIndex, item->children.count(), item->children.count()); + item->children.append(newItem); + q->endInsertRows(); + + item = newItem; + } + } + + sensorInfos[item] = info; +} + +void SensorTreeModel::Private::removeSensor(const QString &sensorId) +{ + SensorTreeItem *item = rootItem; + + const auto segments = sensorId.split(QLatin1Char('/')); + for (const QString &segment : segments) { + int index = item->indexOf(segment); + Q_ASSERT(index != -1); + item = item->children.at(index); + } + + SensorTreeItem *parent = item->parent; + + if (!parent) { + return; + } + + auto remove = [this](SensorTreeItem *item, SensorTreeItem *parent) { + const int index = item->parent->children.indexOf(item); + + const QModelIndex &parentIndex = (parent == rootItem) ? QModelIndex() : q->createIndex(parent->parent->children.indexOf(parent), 0, parent); + q->beginRemoveRows(parentIndex, index, index); + delete item->parent->children.takeAt(index); + q->endRemoveRows(); + + sensorInfos.remove(item); + }; + + remove(item, parent); + + while (!parent->children.count()) { + item = parent; + parent = parent->parent; + + if (!parent) { + break; + } + + remove(item, parent); + } +} + +QString SensorTreeModel::Private::sensorId(const QModelIndex &index) +{ + QStringList segments; + + SensorTreeItem *item = static_cast(index.internalPointer()); + + segments << item->name; + + while (item->parent && item->parent != rootItem) { + item = item->parent; + segments.prepend(item->name); + } + + return segments.join(QLatin1Char('/')); +} + +SensorTreeItem *KSysGuard::SensorTreeModel::Private::find(const QString &sensorId) +{ + auto item = rootItem; + const auto segments = sensorId.split(QLatin1Char('/')); + for (const QString &segment : segments) { + int index = item->indexOf(segment); + if (index != -1) { + item = item->children.at(index); + } else { + return nullptr; + } + } + return item; +} + +SensorTreeModel::SensorTreeModel(QObject *parent) + : QAbstractItemModel(parent) + , d(new Private(this)) +{ + connect(SensorDaemonInterface::instance(), &SensorDaemonInterface::sensorAdded, this, &SensorTreeModel::onSensorAdded); + connect(SensorDaemonInterface::instance(), &SensorDaemonInterface::sensorRemoved, this, &SensorTreeModel::onSensorRemoved); + connect(SensorDaemonInterface::instance(), &SensorDaemonInterface::metaDataChanged, this, &SensorTreeModel::onMetaDataChanged); + init(); +} + +SensorTreeModel::~SensorTreeModel() +{ +} + +QHash SensorTreeModel::roleNames() const +{ + QHash roles = QAbstractItemModel::roleNames(); + + QMetaEnum e = metaObject()->enumerator(metaObject()->indexOfEnumerator("AdditionalRoles")); + + for (int i = 0; i < e.keyCount(); ++i) { + roles.insert(e.value(i), e.key(i)); + } + + return roles; +} + +QVariant SensorTreeModel::headerData(int section, Qt::Orientation, int role) const +{ + if (role != Qt::DisplayRole) { + return QVariant(); + } + + if (section == 0) { + return i18n("Sensor Browser"); + } + + return QVariant(); +} + +QStringList SensorTreeModel::mimeTypes() const +{ + return QStringList() << QStringLiteral("application/x-ksysguard"); +} + +QVariant SensorTreeModel::data(const QModelIndex &index, int role) const +{ + const bool check = checkIndex(index, CheckIndexOption::IndexIsValid); + + Q_ASSERT(check); + + if (!check) { + return QVariant(); + } + + if (role == Qt::DisplayRole) { + SensorTreeItem *item = static_cast(index.internalPointer()); + + if (d->sensorInfos.contains(item)) { + auto info = d->sensorInfos.value(item); + const QString &unit = Formatter::symbol(info.unit); + + if (!unit.isEmpty()) { + return i18nc("Name (unit)", "%1 (%2)", info.name, unit); + } + + return info.name; + } + + const QString &name = item->name; + if (name.isEmpty()) { + return i18n("EMPTY"); + } else { + return name; + } + // Only leaf nodes are valid sensors + } else if (role == SensorId) { + if (rowCount(index)) { + return QString(); + } else { + return d->sensorId(index); + } + } + + return QVariant(); +} + +QMimeData *SensorTreeModel::mimeData(const QModelIndexList &indexes) const +{ + QMimeData *mimeData = new QMimeData(); + + if (indexes.count() != 1) { + return mimeData; + } + + const QModelIndex &index = indexes.at(0); + + const bool check = checkIndex(index, CheckIndexOption::IndexIsValid); + + Q_ASSERT(check); + + if (!check) { + return mimeData; + } + + if (rowCount(index)) { + return mimeData; + } + + mimeData->setData(QStringLiteral("application/x-ksysguard"), d->sensorId(index).toUtf8()); + + return mimeData; +} + +Qt::ItemFlags SensorTreeModel::flags(const QModelIndex &index) const +{ + const bool check = checkIndex(index, CheckIndexOption::IndexIsValid | CheckIndexOption::DoNotUseParent); + + Q_ASSERT(check); + + if (!check) { + return Qt::NoItemFlags; + } + + if (!rowCount(index)) { + return Qt::ItemIsDragEnabled | Qt::ItemIsSelectable | Qt::ItemIsEnabled; + } + + return Qt::ItemIsEnabled; +} + +int SensorTreeModel::rowCount(const QModelIndex &parent) const +{ + if (parent.isValid()) { + const bool check = checkIndex(parent, CheckIndexOption::IndexIsValid | CheckIndexOption::DoNotUseParent); + + Q_ASSERT(check); + + if (!check) { + return 0; + } + + const SensorTreeItem *item = static_cast(parent.internalPointer()); + return item->children.count(); + } + + return d->rootItem->children.count(); +} + +int SensorTreeModel::columnCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent) + + return 1; +} + +QModelIndex SensorTreeModel::index(int row, int column, const QModelIndex &parent) const +{ + SensorTreeItem *parentItem = d->rootItem; + + if (parent.isValid()) { + Q_ASSERT(parent.model() == this); + + if (parent.model() != this) { + return QModelIndex(); + } + + parentItem = static_cast(parent.internalPointer()); + } + + if (row < 0 || row >= parentItem->children.count()) { + return QModelIndex(); + } + + if (column < 0) { + return QModelIndex(); + } + + return createIndex(row, column, parentItem->children.at(row)); +} + +QModelIndex SensorTreeModel::parent(const QModelIndex &index) const +{ + const bool check = checkIndex(index, CheckIndexOption::IndexIsValid | CheckIndexOption::DoNotUseParent); + + Q_ASSERT(check); + + if (!check) { + return QModelIndex(); + } + + if (index.column() > 0) { + return QModelIndex(); + } + + const SensorTreeItem *item = static_cast(index.internalPointer()); + SensorTreeItem *parentItem = item->parent; + + if (parentItem == d->rootItem) { + return QModelIndex(); + } + + return createIndex(parentItem->parent->children.indexOf(parentItem), 0, parentItem); +} + +void SensorTreeModel::init() +{ + auto query = new SensorQuery{QString(), this}; + connect(query, &SensorQuery::finished, [query, this]() { + query->deleteLater(); + const auto result = query->result(); + for (auto pair : result) { + d->addSensor(pair.first, pair.second); + } + }); + query->execute(); +} + +void KSysGuard::SensorTreeModel::onSensorAdded(const QString &sensor) +{ + SensorDaemonInterface::instance()->requestMetaData(sensor); +} + +void KSysGuard::SensorTreeModel::onSensorRemoved(const QString &sensor) +{ + d->removeSensor(sensor); +} + +void KSysGuard::SensorTreeModel::onMetaDataChanged(const QString &sensorId, const SensorInfo &info) +{ + auto item = d->find(sensorId); + if (!item) { + d->addSensor(sensorId, info); + } else { + item->name = info.name; + d->sensorInfos[item] = info; + + auto parentItem = item->parent; + if (!parentItem) { + return; + } + + auto parentIndex = QModelIndex{}; + if (parentItem != d->rootItem) { + parentIndex = createIndex(parentItem->parent->children.indexOf(parentItem), 0, parentItem); + } + + auto itemIndex = index(parentItem->children.indexOf(item), 0, parentIndex); + Q_EMIT dataChanged(itemIndex, itemIndex); + } +} diff --git a/sensors/SensorTreeModel.h b/sensors/SensorTreeModel.h new file mode 100644 index 0000000..7cc8902 --- /dev/null +++ b/sensors/SensorTreeModel.h @@ -0,0 +1,77 @@ +/* + Copyright (c) 2019 Eike Hein + Copyright (C) 2020 Arjen Hiemstra + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#pragma once + +#include "sensors_export.h" +#include +#include + +namespace KSysGuard +{ +class SensorInfo; + +/** + * A model representing a tree of sensors that are available from the daemon. + * + * This model exposes the daemon's sensors as a tree, based on their path. Each + * sensor is assumed to be structured in a format similar to + * `category/object/sensor`. This model will then expose a tree, with `category` + * as top level, `object` below it and finally `sensor` itself. + */ +class SENSORS_EXPORT SensorTreeModel : public QAbstractItemModel +{ + Q_OBJECT + +public: + enum AdditionalRoles { + SensorId = Qt::UserRole + 1, + }; + Q_ENUM(AdditionalRoles) + + explicit SensorTreeModel(QObject *parent = nullptr); + virtual ~SensorTreeModel(); + + QHash roleNames() const override; + QVariant headerData(int section, Qt::Orientation, int role) const override; + QStringList mimeTypes() const override; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + QMimeData *mimeData(const QModelIndexList &indexes) const override; + + Qt::ItemFlags flags(const QModelIndex &index) const override; + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; + QModelIndex parent(const QModelIndex &index) const override; + +private: + void init(); + void onSensorAdded(const QString &sensor); + void onSensorRemoved(const QString &sensor); + void onMetaDataChanged(const QString &sensorId, const SensorInfo &info); + + class Private; + const std::unique_ptr d; +}; + +} diff --git a/sensors/declarative/CMakeLists.txt b/sensors/declarative/CMakeLists.txt new file mode 100644 index 0000000..25ea25c --- /dev/null +++ b/sensors/declarative/CMakeLists.txt @@ -0,0 +1,8 @@ +include_directories(${CMAKE_CURRENT_BINARY_DIR}/.. ${CMAKE_CURRENT_SOURCE_DIR}/..) + +add_library(SensorsPlugin SHARED SensorsPlugin.cpp) + +target_link_libraries(SensorsPlugin Qt5::Qml KSysGuard::Sensors) + +install(TARGETS SensorsPlugin DESTINATION ${KDE_INSTALL_QMLDIR}/org/kde/ksysguard/sensors) +install(FILES qmldir ExtendedLegend.qml DESTINATION ${KDE_INSTALL_QMLDIR}/org/kde/ksysguard/sensors) diff --git a/sensors/declarative/ExtendedLegend.qml b/sensors/declarative/ExtendedLegend.qml new file mode 100644 index 0000000..a772aab --- /dev/null +++ b/sensors/declarative/ExtendedLegend.qml @@ -0,0 +1,70 @@ +/* + * Copyright 2019 Marco Martin + * Copyright 2019 David Edmundson + * Copyright 2019 Arjen Hiemstra + * Copyright 2019 Kai Uwe Broulik + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 General Public License for more details + * + * You should have received a copy of the GNU Library 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.9 +import QtQuick.Layouts 1.1 + +import org.kde.kirigami 2.8 as Kirigami + +import org.kde.ksysguard.formatter 1.0 +import org.kde.ksysguard.sensors 1.0 + +import org.kde.quickcharts 1.0 as Charts +import org.kde.quickcharts.controls 1.0 as ChartsControls + +ChartsControls.Legend { + id: legend + + property alias textOnlySensorIds: textOnlySensorsRepeater.model + property var sourceModel + property var colorSource + + flow: GridLayout.TopToBottom + + Layout.maximumHeight: implicitHeight + Layout.maximumWidth: parent.width + + spacing: Kirigami.Units.smallSpacing + + valueVisible: true + valueWidth: units.gridUnit * 2 + formatValue: function(input, index) { + return Formatter.formatValueShowNull(input, sourceModel.data(sourceModel.index(0, index), SensorDataModel.Unit)) + } + + Repeater { + id: textOnlySensorsRepeater + delegate: ChartsControls.LegendDelegate { + name: sensor.shortName + value: sensor.formattedValue || "" + colorVisible: false + + layoutWidth: legend.width + valueWidth: units.gridUnit * 2 + + Sensor { + id: sensor + sensorId: modelData + } + } + } +} diff --git a/processcore/formatter.h b/sensors/declarative/SensorsPlugin.cpp similarity index 59% copy from processcore/formatter.h copy to sensors/declarative/SensorsPlugin.cpp index 32af39d..bbd6a12 100644 --- a/processcore/formatter.h +++ b/sensors/declarative/SensorsPlugin.cpp @@ -1,20 +1,37 @@ /* - Copyright (C) 2019 Vlad Zagorodniy + Copyright (C) 2020 Arjen Hiemstra This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "formatter/Formatter.h" +#include "SensorsPlugin.h" + +#include "Sensor.h" +#include "SensorDataModel.h" +#include "SensorTreeModel.h" + +#include + +using namespace KSysGuard; + +void SensorsPlugin::registerTypes(const char *uri) +{ + Q_ASSERT(QLatin1String(uri) == QLatin1String("org.kde.ksysguard.sensors")); + + qmlRegisterType(uri, 1, 0, "SensorDataModel"); + qmlRegisterType(uri, 1, 0, "SensorTreeModel"); + qmlRegisterType(uri, 1, 0, "Sensor"); +} diff --git a/processcore/formatter.h b/sensors/declarative/SensorsPlugin.h similarity index 72% copy from processcore/formatter.h copy to sensors/declarative/SensorsPlugin.h index 32af39d..2a24a9d 100644 --- a/processcore/formatter.h +++ b/sensors/declarative/SensorsPlugin.h @@ -1,20 +1,31 @@ /* - Copyright (C) 2019 Vlad Zagorodniy + Copyright (C) 2020 Arjen Hiemstra This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "formatter/Formatter.h" +#pragma once + +#include + +class SensorsPlugin : public QQmlExtensionPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface") + +public: + void registerTypes(const char *uri) override; +}; diff --git a/sensors/declarative/qmldir b/sensors/declarative/qmldir new file mode 100644 index 0000000..dd44d55 --- /dev/null +++ b/sensors/declarative/qmldir @@ -0,0 +1,3 @@ +module org.kde.ksysguard.sensors +plugin SensorsPlugin +ExtendedLegend 1.0 ExtendedLegend.qml diff --git a/sensors/org.kde.KSysGuardDaemon.xml b/sensors/org.kde.KSysGuardDaemon.xml new file mode 100644 index 0000000..b242333 --- /dev/null +++ b/sensors/org.kde.KSysGuardDaemon.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +