├── .gitignore ├── install ├── reinstall ├── package ├── contents │ ├── ui │ │ ├── lib │ │ │ ├── JsonTableString.qml │ │ │ ├── TextLabel.qml │ │ │ ├── JsonTableInt.qml │ │ │ ├── ConfigSection.qml │ │ │ ├── ScrollView1.qml │ │ │ ├── JsonTableView.qml │ │ │ ├── ConfigPage.qml │ │ │ ├── JsonTableStringList.qml │ │ │ ├── ConfigSpinBox.qml │ │ │ ├── AppletIcon.qml │ │ │ ├── AppletVersion.qml │ │ │ ├── ExecUtil.qml │ │ │ └── JsonTableTextField.qml │ │ ├── DiskIOGraph.qml │ │ ├── NetworkIOGraph.qml │ │ ├── SimpleProgressBar.qml │ │ ├── DiskMonitor.qml │ │ ├── SensorPresets.qml │ │ ├── config │ │ │ ├── ConfigSettings.qml │ │ │ ├── ConfigNetworks.qml │ │ │ └── ConfigSensors.qml │ │ ├── PlotDataObj.qml │ │ ├── DeviceData.qml │ │ ├── SensorDetector.qml │ │ ├── NewUserSplash.qml │ │ ├── JsonTableSensor.qml │ │ ├── NetworkListDetector.qml │ │ ├── DiskUsageBar.qml │ │ ├── ListBlockDevices.qml │ │ ├── PartitionUsageBar.qml │ │ ├── main.qml │ │ ├── PlotterCanvas.qml │ │ ├── DashView.qml │ │ ├── SensorData.qml │ │ └── SensorGraph.qml │ ├── config │ │ ├── config.qml │ │ └── main.xml │ └── icons │ │ ├── fan.svg │ │ └── amd-logo.svg ├── metadata.desktop └── translate │ ├── ReadMe.md │ ├── build │ ├── install │ ├── plasmoidlocaletest │ ├── template.pot │ ├── merge │ └── nl_NL.po ├── ReadMe.md └── Changelog.md /.gitignore: -------------------------------------------------------------------------------- 1 | *.plasmoid 2 | *.qmlc 3 | *.jsc 4 | *.mo 5 | -------------------------------------------------------------------------------- /install: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Version 2 3 | 4 | kpackagetool5 -t Plasma/Applet -i package 5 | -------------------------------------------------------------------------------- /reinstall: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Version 2 3 | 4 | kpackagetool5 -t Plasma/Applet -u package 5 | killall plasmashell 6 | kstart5 plasmashell 7 | -------------------------------------------------------------------------------- /package/contents/ui/lib/JsonTableString.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Controls 1.0 3 | import QtQuick.Layouts 1.0 4 | 5 | TableViewColumn { 6 | id: tableViewColumn 7 | 8 | movable: false 9 | 10 | property string placeholderText: "" 11 | 12 | delegate: JsonTableTextField { 13 | placeholderText: tableViewColumn.placeholderText 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /package/contents/ui/lib/TextLabel.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import org.kde.plasma.components 2.0 as PlasmaComponents 3 | 4 | // Fix some of the weirdness in PlasmaComponents.Label 5 | // https://github.com/KDE/plasma-framework/blob/master/src/declarativeimports/plasmacomponents/qml/Label.qml 6 | PlasmaComponents.Label { 7 | height: paintedHeight // Plasma sets the minHeight to 1.6*fontHeight 8 | } 9 | -------------------------------------------------------------------------------- /package/contents/ui/lib/JsonTableInt.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Controls 1.0 3 | import QtQuick.Layouts 1.0 4 | 5 | TableViewColumn { 6 | id: tableViewColumn 7 | 8 | movable: false 9 | 10 | property string placeholderText: "" 11 | 12 | delegate: JsonTableTextField { 13 | placeholderText: tableViewColumn.placeholderText 14 | 15 | function setterValue() { 16 | return parseInt(text, 10) 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /ReadMe.md: -------------------------------------------------------------------------------- 1 | # System Monitor Dashboard 2 | 3 | * Panel "icon" is an animated version of the KSysGuard icon that shows your current CPU usage. 4 | * Disk Drives are parsed from the command `lsblk --output-all --json`. 5 | * Uses the fullscreen popup window from the Application Dashboard widget. 6 | * Can quickly setup lm_sensors temp graphs and fan graphs in the right most column. 7 | 8 | ## Screenshots 9 | 10 | ![](https://i.imgur.com/U0BuqVe.png) 11 | 12 | -------------------------------------------------------------------------------- /package/contents/ui/DiskIOGraph.qml: -------------------------------------------------------------------------------- 1 | SensorGraph { 2 | property string partitionId 3 | sensors: [ 4 | "disk/" + partitionId + "/Rate/rblk", 5 | "disk/" + partitionId + "/Rate/wblk", 6 | ] 7 | legendLabels: [ 8 | i18n("Read"), 9 | i18n("Write"), 10 | ] 11 | colors: [ 12 | "#094", 13 | "#8fc", 14 | ] 15 | label: "Disk" 16 | sublabel: partitionId 17 | 18 | function formatLabel(value, units) { 19 | return humanReadableBytes(value) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /package/contents/ui/lib/ConfigSection.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Controls 1.0 3 | import QtQuick.Layouts 1.0 4 | 5 | GroupBox { 6 | id: control 7 | Layout.fillWidth: true 8 | default property alias _contentChildren: content.data 9 | property string label 10 | 11 | ColumnLayout { 12 | id: content 13 | Layout.fillWidth: true 14 | 15 | Text { 16 | visible: control.label 17 | text: control.label 18 | font.bold: true 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /package/contents/ui/NetworkIOGraph.qml: -------------------------------------------------------------------------------- 1 | SensorGraph { 2 | property string interfaceName 3 | sensors: [ 4 | "network/interfaces/" + interfaceName + "/receiver/data", 5 | "network/interfaces/" + interfaceName + "/transmitter/data", 6 | ] 7 | legendLabels: [ 8 | i18n("Download"), 9 | i18n("Upload"), 10 | ] 11 | colors: [ 12 | "#80b", 13 | "#b08", 14 | ] 15 | label: i18n("Network") 16 | sublabel: interfaceName 17 | 18 | function formatLabel(value, units) { 19 | return humanReadableBytes(value) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /package/contents/config/config.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | 3 | import org.kde.plasma.configuration 2.0 4 | 5 | ConfigModel { 6 | ConfigCategory { 7 | name: i18n("Sensors") 8 | icon: "utilities-system-monitor" 9 | source: "config/ConfigSensors.qml" 10 | } 11 | ConfigCategory { 12 | name: i18n("Networks") 13 | icon: "network-wireless" 14 | source: "config/ConfigNetworks.qml" 15 | } 16 | ConfigCategory { 17 | name: i18n("Settings") 18 | icon: "configure" 19 | source: "config/ConfigSettings.qml" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /package/contents/ui/SimpleProgressBar.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | 3 | Rectangle { 4 | id: simpleProgressBar 5 | color: "#88E6E6E6" 6 | border.width: 1 7 | border.color: "#88D0D0D0" 8 | 9 | property real value: 0 10 | property real maxValue: 100 11 | readonly property real ratio: maxValue > 0 ? value / maxValue : 0 12 | 13 | Rectangle { 14 | anchors.left: parent.left 15 | anchors.top: parent.top 16 | anchors.bottom: parent.bottom 17 | width: parent.width * ratio 18 | color: ratio < 0.95 ? "#26a0da" : "#da2626" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /package/contents/ui/lib/ScrollView1.qml: -------------------------------------------------------------------------------- 1 | // Version 1 2 | 3 | import QtQuick 2.1 4 | import QtQuick.Controls 1.1 5 | 6 | ScrollView { 7 | id: scrollView 8 | 9 | readonly property int contentWidth: contentItem ? contentItem.width : width 10 | readonly property int contentHeight: contentItem ? contentItem.height : 0 // Warning: Binding loop 11 | readonly property int viewportWidth: viewport ? viewport.width : width 12 | readonly property int viewportHeight: viewport ? viewport.height : height 13 | readonly property int scrollY: flickableItem ? flickableItem.contentY : 0 14 | } 15 | -------------------------------------------------------------------------------- /package/contents/ui/DiskMonitor.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.1 2 | import QtQuick.Layouts 1.3 3 | 4 | ColumnLayout { 5 | id: diskMonitor 6 | spacing: units.smallSpacing 7 | 8 | property alias icon: ioGraph.icon 9 | property alias iconOverlays: ioGraph.iconOverlays 10 | property alias label: ioGraph.label 11 | property alias sublabel: ioGraph.sublabel 12 | property alias partitionId: ioGraph.partitionId 13 | 14 | property alias partitionPaths: usageBar.partitionPaths 15 | 16 | DiskIOGraph { 17 | id: ioGraph 18 | icon: "drive-harddisk" 19 | } 20 | DiskUsageBar { 21 | id: usageBar 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /package/metadata.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Name=System Monitor Dashboard 3 | Comment=A fullscreen list of graphs over time for various hardware sensors. 4 | 5 | Type=Service 6 | Icon=utilities-system-monitor 7 | X-KDE-ServiceTypes=Plasma/Applet 8 | 9 | X-Plasma-API=declarativeappletscript 10 | X-Plasma-MainScript=ui/main.qml 11 | 12 | X-KDE-PluginInfo-Author=Chris Holland 13 | X-KDE-PluginInfo-Email=zrenfire@gmail.com 14 | X-KDE-PluginInfo-Name=com.github.zren.sysmonitordash 15 | X-KDE-PluginInfo-Version=4 16 | X-KDE-PluginInfo-Website=https://github.com/Zren/plasma-applet-sysmonitordash 17 | X-KDE-PluginInfo-Category=System Information 18 | X-KDE-PluginInfo-Depends= 19 | -------------------------------------------------------------------------------- /package/contents/ui/lib/JsonTableView.qml: -------------------------------------------------------------------------------- 1 | // Version 2 2 | 3 | import QtQuick 2.0 4 | import QtQuick.Controls 1.0 5 | import QtQuick.Layouts 1.0 6 | 7 | TableView { 8 | id: tableView 9 | signal cellChanged(int row, int cell, string role) 10 | signal rowAdded(int indexAdded) 11 | signal rowRemoved(int indexRemoved) 12 | 13 | 14 | function addRow(obj) { 15 | if (typeof obj === "undefined") { 16 | obj = {} 17 | } 18 | var newLength = tableView.model.push(obj) 19 | var newIndex = newLength - 1 20 | rowAdded(newIndex) 21 | tableView.modelChanged() 22 | } 23 | 24 | function removeRow(rowIndex) { 25 | tableView.model.splice(rowIndex, 1) 26 | rowRemoved(rowIndex) 27 | tableView.modelChanged() 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /package/contents/ui/SensorPresets.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | 3 | QtObject { 4 | function getPreset(sensorName) { 5 | var match = sensorName.match(/^lmsensors\/((.+)\/(fan\d+))$/) 6 | if (match) { 7 | var fanPreset = { 8 | icon: 'fan', 9 | colors: ["#888"], 10 | label: i18n("Fan"), 11 | sublabel: match[1], 12 | defaultMax: 2000, 13 | } 14 | return fanPreset 15 | } 16 | 17 | var match = sensorName.match(/^lmsensors\/((.+)\/(temp\d+))$/) 18 | if (match) { 19 | var tempPreset = { 20 | colors: ["#800"], 21 | label: i18n("Temp"), 22 | sublabel: match[1], 23 | defaultMax: 70, 24 | } 25 | var chipName = match[2] 26 | if (chipName.match(/^radeon-/) || chipName.match(/^amdgpu-/)) { 27 | tempPreset.icon = 'amd-logo' 28 | tempPreset.label = i18n("GPU") 29 | } 30 | 31 | return tempPreset 32 | } 33 | 34 | // Default 35 | return { 36 | label: sensorName, 37 | colors: ["#888"], 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /package/contents/ui/lib/ConfigPage.qml: -------------------------------------------------------------------------------- 1 | // Version 4 2 | 3 | import QtQuick 2.0 4 | import QtQuick.Layouts 1.0 5 | 6 | Item { 7 | id: page 8 | Layout.fillWidth: true 9 | default property alias _contentChildren: content.data 10 | implicitHeight: content.implicitHeight 11 | 12 | ColumnLayout { 13 | id: content 14 | anchors.left: parent.left 15 | anchors.right: parent.right 16 | anchors.top: parent.top 17 | 18 | // Workaround for crash when using default on a Layout. 19 | // https://bugreports.qt.io/browse/QTBUG-52490 20 | // Still affecting Qt 5.7.0 21 | Component.onDestruction: { 22 | while (children.length > 0) { 23 | children[children.length - 1].parent = page 24 | } 25 | } 26 | } 27 | 28 | property alias showAppletVersion: appletVersionLoader.active 29 | Loader { 30 | id: appletVersionLoader 31 | active: false 32 | visible: active 33 | source: "AppletVersion.qml" 34 | anchors.right: parent.right 35 | anchors.bottom: parent.top 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /package/contents/ui/config/ConfigSettings.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Controls 1.0 3 | import QtQuick.Layouts 1.0 4 | 5 | import ".." 6 | import "../lib" 7 | 8 | ConfigPage { 9 | ConfigSection { 10 | label: i18n("Dashboard Sensors") 11 | 12 | ConfigSpinBox { 13 | before: i18n("Update Every:") 14 | configKey: 'dashUpdateInterval' 15 | minimumValue: 50 16 | stepSize: 50 17 | suffix: i18n("ms") 18 | } 19 | ConfigSpinBox { 20 | before: i18n("Visible Duration:") 21 | configKey: 'dashVisibleDuration' 22 | minimumValue: 5 23 | stepSize: 5 24 | suffix: i18n("sec") 25 | } 26 | } 27 | ConfigSection { 28 | label: i18n("Panel Icon") 29 | 30 | ConfigSpinBox { 31 | id: iconUpdateInterval 32 | before: i18n("Update Every:") 33 | configKey: 'iconUpdateInterval' 34 | minimumValue: 50 35 | stepSize: 50 36 | suffix: i18n("ms") 37 | } 38 | ConfigSpinBox { 39 | before: i18n("Visible Duration:") 40 | enabled: false 41 | value: iconUpdateInterval.configValue * 5 / 1000 42 | suffix: i18n("sec") 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /package/contents/ui/lib/JsonTableStringList.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Controls 1.0 3 | import QtQuick.Layouts 1.0 4 | 5 | TableViewColumn { 6 | id: tableViewColumn 7 | 8 | movable: false 9 | 10 | property string placeholderText: "" 11 | 12 | delegate: JsonTableTextField { 13 | placeholderText: tableViewColumn.placeholderText 14 | 15 | function hasKey() { 16 | return (typeof tableView.model[styleData.row] !== "undefined" 17 | && typeof tableView.model[styleData.row][tableViewColumn.role] !== "undefined" 18 | ) 19 | } 20 | 21 | readonly property var cellValue: { 22 | if (hasKey()) { 23 | return tableView.model[styleData.row][tableViewColumn.role] 24 | } else { 25 | return '' 26 | } 27 | } 28 | function getter() { 29 | // console.log('StringList.getter', cellValue) 30 | return cellValue ? cellValue.join(',') : '' 31 | } 32 | 33 | function setterValue() { 34 | if (text) { 35 | return text.split(',') 36 | } else { 37 | if (hasKey()) { 38 | return [] 39 | } else { 40 | return undefined 41 | } 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /package/contents/ui/PlotDataObj.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | 3 | // Based on KQuickAddons.PlotData 4 | // https://github.com/KDE/kdeclarative/blob/master/src/qmlcontrols/kquickcontrolsaddons/plotter.h 5 | // https://github.com/KDE/kdeclarative/blob/master/src/qmlcontrols/kquickcontrolsaddons/plotter.cpp 6 | QtObject { 7 | id: plotData 8 | 9 | property string label: '' 10 | property color color: '#000' 11 | property var values: [] 12 | property real max: 0 13 | property real min: 0 14 | 15 | property int sampleSize: 0 16 | property var normalizedValues: [] 17 | 18 | function addSample(value) { 19 | if (values.length >= sampleSize) { 20 | values.shift() 21 | } 22 | 23 | values.push(Number(value)) 24 | 25 | max = Math.max.apply(null, values) 26 | min = Math.min.apply(null, values) 27 | 28 | valuesChanged() 29 | } 30 | 31 | function setSampleSize(size) { 32 | if (sampleSize == size) { 33 | return 34 | } 35 | 36 | if (values.length > size) { 37 | var numberToRemove = values.length - size 38 | for (var i = 0; i < numberToRemove; i++) { 39 | values.shift() 40 | } 41 | } else if (values.length < size) { 42 | var numberToAdd = size - values.length 43 | for (var i = 0; i < numberToAdd; i++) { 44 | values.unshift(0) 45 | } 46 | } 47 | 48 | sampleSize = size 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /package/contents/ui/lib/ConfigSpinBox.qml: -------------------------------------------------------------------------------- 1 | // Version 3 2 | 3 | import QtQuick 2.0 4 | import QtQuick.Controls 1.0 5 | import QtQuick.Layouts 1.0 6 | 7 | RowLayout { 8 | id: configSpinBox 9 | 10 | property string configKey: '' 11 | readonly property var configValue: configKey ? plasmoid.configuration[configKey] : 0 12 | property alias decimals: spinBox.decimals 13 | property alias horizontalAlignment: spinBox.horizontalAlignment 14 | property alias maximumValue: spinBox.maximumValue 15 | property alias minimumValue: spinBox.minimumValue 16 | property alias prefix: spinBox.prefix 17 | property alias stepSize: spinBox.stepSize 18 | property alias suffix: spinBox.suffix 19 | property alias value: spinBox.value 20 | 21 | property alias before: labelBefore.text 22 | property alias after: labelAfter.text 23 | 24 | Label { 25 | id: labelBefore 26 | text: "" 27 | visible: text 28 | } 29 | 30 | SpinBox { 31 | id: spinBox 32 | 33 | value: configValue 34 | onValueChanged: serializeTimer.start() 35 | maximumValue: 2147483647 36 | } 37 | 38 | Label { 39 | id: labelAfter 40 | text: "" 41 | visible: text 42 | } 43 | 44 | Timer { // throttle 45 | id: serializeTimer 46 | interval: 300 47 | onTriggered: { 48 | if (configKey) { 49 | plasmoid.configuration[configKey] = value 50 | } 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /package/contents/ui/lib/AppletIcon.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import org.kde.plasma.core 2.0 as PlasmaCore 3 | 4 | Item { 5 | id: appletIcon 6 | property string source: '' 7 | property bool active: false 8 | readonly property bool usingPackageSvg: filename // plasmoid.file() returns "" if file doesn't exist. 9 | readonly property string filename: source ? plasmoid.file("", "icons/" + source + '.svg') : "" 10 | readonly property int minSize: Math.min(width, height) 11 | property alias overlays: overlayItem.overlays 12 | 13 | PlasmaCore.IconItem { 14 | id: overlayItem 15 | anchors.fill: parent 16 | } 17 | 18 | PlasmaCore.IconItem { 19 | anchors.fill: parent 20 | visible: !appletIcon.usingPackageSvg 21 | source: appletIcon.usingPackageSvg ? '' : appletIcon.source 22 | active: appletIcon.active 23 | } 24 | 25 | PlasmaCore.SvgItem { 26 | id: svgItem 27 | anchors.centerIn: parent 28 | readonly property real maxSize: Math.min(naturalSize.width, naturalSize.height) 29 | readonly property real widthRatio: naturalSize.width / maxSize 30 | readonly property real heightRatio: naturalSize.height / maxSize 31 | width: appletIcon.minSize * widthRatio 32 | height: appletIcon.minSize * heightRatio 33 | 34 | visible: appletIcon.usingPackageSvg 35 | svg: PlasmaCore.Svg { 36 | id: svg 37 | imagePath: appletIcon.filename 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /package/contents/ui/lib/AppletVersion.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Controls 1.0 3 | import QtQuick.Layouts 1.0 4 | import org.kde.plasma.core 2.0 as PlasmaCore 5 | import org.kde.plasma.plasmoid 2.0 6 | 7 | Item { 8 | implicitWidth: label.implicitWidth 9 | implicitHeight: label.implicitHeight 10 | 11 | property string version: "?" 12 | property string metadataFilepath: plasmoid.file("", "../metadata.desktop") 13 | 14 | PlasmaCore.DataSource { 15 | id: executable 16 | engine: "executable" 17 | connectedSources: [] 18 | onNewData: { 19 | var exitCode = data["exit code"] 20 | var exitStatus = data["exit status"] 21 | var stdout = data["stdout"] 22 | var stderr = data["stderr"] 23 | exited(exitCode, exitStatus, stdout, stderr) 24 | disconnectSource(sourceName) // cmd finished 25 | } 26 | function exec(cmd) { 27 | connectSource(cmd) 28 | } 29 | signal exited(int exitCode, int exitStatus, string stdout, string stderr) 30 | } 31 | 32 | Connections { 33 | target: executable 34 | onExited: { 35 | version = stdout.replace('\n', ' ').trim() 36 | } 37 | } 38 | 39 | Label { 40 | id: label 41 | text: i18n("Version: %1", version) 42 | } 43 | 44 | Component.onCompleted: { 45 | var cmd = 'kreadconfig5 --file "' + metadataFilepath + '" --group "Desktop Entry" --key "X-KDE-PluginInfo-Version"' 46 | executable.exec(cmd) 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /package/contents/ui/lib/ExecUtil.qml: -------------------------------------------------------------------------------- 1 | // Version 3 2 | 3 | import QtQuick 2.0 4 | import org.kde.plasma.core 2.0 as PlasmaCore 5 | 6 | PlasmaCore.DataSource { 7 | id: executable 8 | engine: "executable" 9 | connectedSources: [] 10 | onNewData: { 11 | var exitCode = data["exit code"] 12 | var exitStatus = data["exit status"] 13 | var stdout = data["stdout"] 14 | var stderr = data["stderr"] 15 | exited(sourceName, exitCode, exitStatus, stdout, stderr) 16 | disconnectSource(sourceName) // cmd finished 17 | } 18 | 19 | signal exited(string cmd, int exitCode, int exitStatus, string stdout, string stderr) 20 | 21 | function trimOutput(stdout) { 22 | return stdout.replace('\n', ' ').trim() 23 | } 24 | 25 | property var listeners: ({}) // Empty Map 26 | 27 | function exec(cmd, callback) { 28 | if (typeof callback === 'function') { 29 | if (listeners[cmd]) { // Our implementation only allows 1 callback per command. 30 | exited.disconnect(listeners[cmd]) 31 | delete listeners[cmd] 32 | } 33 | var listener = execCallback.bind(executable, callback) 34 | exited.connect(listener) 35 | listeners[cmd] = listener 36 | } 37 | connectSource(cmd) 38 | } 39 | 40 | function execCallback(callback, cmd, exitCode, exitStatus, stdout, stderr) { 41 | exited.disconnect(listeners[cmd]) 42 | delete listeners[cmd] 43 | callback(cmd, exitCode, exitStatus, stdout, stderr) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /package/contents/ui/DeviceData.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Controls 1.0 3 | import QtQuick.Layouts 1.0 4 | 5 | import org.kde.plasma.core 2.0 as PlasmaCore 6 | 7 | Item { 8 | id: deviceData 9 | 10 | readonly property string processorProduct: sdSource.data[processorUdi]['Product'] 11 | 12 | 13 | // This might be a constant, but I can't guarantee it. 14 | readonly property string processorUdi: '/org/kde/solid/udev/sys/devices/system/cpu/cpu0' 15 | 16 | // You can debug SolidDevice with `solid-hardware list` and `solid-hardware details ...` 17 | PlasmaCore.DataSource { 18 | id: sdSource 19 | engine: "soliddevice" 20 | connectedSources: [ 21 | processorUdi, 22 | ] 23 | interval: 0 24 | 25 | // onSourceAdded: { 26 | // console.log('sdSource.onSourceAdded', source) 27 | // // disconnectSource(source) 28 | // // connectSource(source) 29 | // var propKeys = Object.keys(sdSource.data[processorUdi]) 30 | // console.log(propKeys) 31 | // for (var i = 0; i < propKeys.length; i++) { 32 | // logProp(source, propKeys[i]) 33 | // } 34 | // } 35 | // function logProp(udi, key) { 36 | // console.log(udi, key + ':\t', sdSource.data[udi][key]) 37 | // } 38 | 39 | // onSourceRemoved: { 40 | // console.log('sdSource.onSourceRemoved', source) 41 | // // disconnectSource(source) 42 | // } 43 | 44 | // Component.onCompleted: console.log('sdSource.sources', sources) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /package/contents/ui/lib/JsonTableTextField.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.4 2 | import QtQuick.Controls 1.0 3 | import QtQuick.Layouts 1.0 4 | 5 | TextField { 6 | id: textField 7 | 8 | function hasKey() { 9 | return (typeof tableView.model[styleData.row] !== "undefined" 10 | && typeof tableView.model[styleData.row][tableViewColumn.role] !== "undefined" 11 | ) 12 | } 13 | 14 | readonly property var cellValue: { 15 | if (hasKey()) { 16 | return tableView.model[styleData.row][tableViewColumn.role] 17 | } else { 18 | return '' 19 | } 20 | } 21 | 22 | function getter() { 23 | return cellValue || '' 24 | } 25 | function setterValue() { 26 | return text 27 | } 28 | 29 | text: getter() 30 | 31 | function doValueChange() { 32 | var oldValue = tableView.model[styleData.row][tableViewColumn.role] 33 | var newValue = setterValue() 34 | if (oldValue != newValue) { 35 | tableView.model[styleData.row][tableViewColumn.role] = newValue 36 | tableView.cellChanged(styleData.row, styleData.column, tableViewColumn.role) 37 | } 38 | } 39 | // onTextChanged: {} 40 | onEditingFinished: { 41 | doValueChange() 42 | } 43 | onFocusChanged: { 44 | if (focus) { 45 | // tableView.selection.clear() 46 | // tableView.selection.select(styleData.row, styleData.row) 47 | } 48 | } 49 | 50 | TextMetrics { 51 | id: textMetrics 52 | font: textField.font 53 | text: textField.displayText 54 | } 55 | property int stylePadding: 8 // Guestimate 56 | implicitWidth: textMetrics.advanceWidth + (stylePadding * 2) 57 | } 58 | -------------------------------------------------------------------------------- /package/contents/ui/SensorDetector.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import org.kde.plasma.core 2.0 as PlasmaCore 3 | 4 | QtObject { 5 | id: sensorDetector 6 | 7 | // https://github.com/KDE/plasma-framework/blob/master/src/declarativeimports/core/datasource.h 8 | property var dataSource: PlasmaCore.DataSource { 9 | id: dataSource 10 | engine: "systemmonitor" 11 | 12 | connectedSources: [] 13 | 14 | // Note: DataSource.sources is not always populated on Component.onCompleted, 15 | // but sometimes it is... 16 | // Note: The SystemMonitor widgets scan for sensorNames using the 17 | // onSourceAdded signal, however any sensor already connected by 18 | // another DataSource instance, like in our SensorData instance, 19 | // will not have onSourceAdded called for that sensor. 20 | onSourcesChanged: { 21 | // console.log('sensorDetector.dataSource.onSourcesChanged', dataSource.sources) 22 | sensorDetector.updateModel() 23 | 24 | } 25 | Component.onCompleted: { 26 | // console.log('sensorDetector.dataSource.sources', dataSource.sources) 27 | sensorDetector.updateModel() 28 | } 29 | } 30 | 31 | function updateModel() { 32 | privateModel.clear() 33 | for (var i = 0; i < dataSource.sources.length; i++) { 34 | var sourceName = dataSource.sources[i] 35 | privateModel.append({ 36 | name: sourceName 37 | }) 38 | } 39 | sensorDetector.model = privateModel 40 | } 41 | 42 | property var privateModel: ListModel { 43 | id: privateModel 44 | } 45 | 46 | property var model: [] 47 | } 48 | -------------------------------------------------------------------------------- /package/contents/ui/NewUserSplash.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.1 2 | import QtQuick.Controls 1.1 3 | import QtQuick.Layouts 1.3 4 | import org.kde.plasma.components 2.0 as PlasmaComponents 5 | 6 | import "lib" 7 | 8 | Item { 9 | id: newUserSplash 10 | visible: config.sensorModel.length == 0 11 | 12 | // We need to expose this to disable the dashboard close on click. 13 | property alias configureButton: configureButton 14 | 15 | property color textColor: "#eeeff0" 16 | 17 | Layout.fillWidth: true 18 | 19 | implicitWidth: layout.implicitWidth 20 | implicitHeight: layout.implicitHeight 21 | 22 | Rectangle { 23 | anchors.fill: parent 24 | color: Qt.rgba(0, 0, 0, 0.2) 25 | border.width: 1 26 | border.color: Qt.rgba(0, 0, 0, 0.8) 27 | } 28 | 29 | ColumnLayout { 30 | id: layout 31 | anchors.fill: parent 32 | 33 | TextLabel { 34 | Layout.fillWidth: true 35 | Layout.margins: units.largeSpacing 36 | Layout.bottomMargin: 0 37 | text: i18n("You can hide disks and networks in the config. You can also monitor temperature and fan speed sensors if you have lm-sensors installed.") 38 | color: newUserSplash.textColor 39 | font.pointSize: theme.defaultFont.pointSize * 1.2 40 | horizontalAlignment: Text.AlignHCenter 41 | wrapMode: Text.Wrap 42 | } 43 | 44 | PlasmaComponents.Button { 45 | id: configureButton 46 | Layout.alignment: Qt.AlignHCenter 47 | Layout.margins: units.largeSpacing 48 | text: i18n("Configure Sensors") 49 | iconSource: "configure" 50 | onClicked: { 51 | window.close() 52 | plasmoid.action("configure").trigger() 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /package/contents/icons/fan.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 12 | 13 | 18 | 19 | -------------------------------------------------------------------------------- /package/translate/ReadMe.md: -------------------------------------------------------------------------------- 1 | > Version 6 of Zren's i18n scripts. 2 | 3 | With KDE Frameworks v5.37 and above, translations are bundled with the `*.plasmoid` file downloaded from the store. 4 | 5 | ## Install Translations 6 | 7 | Go to `~/.local/share/plasma/plasmoids/com.github.zren.sysmonitordash/translate/` and run `sh ./build --restartplasma`. 8 | 9 | ## New Translations 10 | 11 | 1. Fill out [`template.pot`](template.pot) with your translations then open a [new issue](https://github.com/Zren/plasma-applet-sysmonitordash/issues/new), name the file `spanish.txt`, attach the txt file to the issue (drag and drop). 12 | 13 | Or if you know how to make a pull request 14 | 15 | 1. Copy the `template.pot` file and name it your locale's code (Eg: `en`/`de`/`fr`) with the extension `.po`. Then fill out all the `msgstr ""`. 16 | 17 | ## Scripts 18 | 19 | * `sh ./merge` will parse the `i18n()` calls in the `*.qml` files and write it to the `template.pot` file. Then it will merge any changes into the `*.po` language files. 20 | * `sh ./build` will convert the `*.po` files to it's binary `*.mo` version and move it to `contents/locale/...` which will bundle the translations in the `*.plasmoid` without needing the user to manually install them. 21 | * `sh ./plasmoidlocaletest` will run `./build` then `plasmoidviewer` (part of `plasma-sdk`). 22 | 23 | ## Links 24 | 25 | * https://techbase.kde.org/Development/Tutorials/Localization/i18n_Build_Systems 26 | 27 | ## Examples 28 | 29 | * https://websvn.kde.org/trunk/l10n-kf5/fr/messages/kde-workspace/ 30 | * https://github.com/psifidotos/nowdock-plasmoid/tree/master/po 31 | * https://github.com/kotelnik/plasma-applet-redshift-control/tree/master/translations 32 | 33 | ## Status 34 | | Locale | Lines | % Done| 35 | |----------|---------|-------| 36 | | Template | 46 | | 37 | | nl_NL | 46/46 | 100% | 38 | -------------------------------------------------------------------------------- /package/translate/build: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Version: 5 3 | 4 | # This script will convert the *.po files to *.mo files, rebuilding the package/contents/locale folder. 5 | # Feature discussion: https://phabricator.kde.org/D5209 6 | # Eg: contents/locale/fr_CA/LC_MESSAGES/plasma_applet_org.kde.plasma.eventcalendar.mo 7 | 8 | DIR=`cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd` 9 | plasmoidName=`kreadconfig5 --file="$DIR/../metadata.desktop" --group="Desktop Entry" --key="X-KDE-PluginInfo-Name"` 10 | website=`kreadconfig5 --file="$DIR/../metadata.desktop" --group="Desktop Entry" --key="X-KDE-PluginInfo-Website"` 11 | bugAddress="$website" 12 | packageRoot=".." # Root of translatable sources 13 | projectName="plasma_applet_${plasmoidName}" # project name 14 | 15 | #--- 16 | if [ -z "$plasmoidName" ]; then 17 | echo "[build] Error: Couldn't read plasmoidName." 18 | exit 19 | fi 20 | 21 | if [ -z "$(which msgfmt)" ]; then 22 | echo "[build] Error: msgfmt command not found. Need to install gettext" 23 | echo "[build] Running 'sudo apt install gettext'" 24 | sudo apt install gettext 25 | echo "[build] gettext installation should be finished. Going back to installing translations." 26 | fi 27 | 28 | #--- 29 | echo "[build] Compiling messages" 30 | 31 | catalogs=`find . -name '*.po'` 32 | for cat in $catalogs; do 33 | echo "$cat" 34 | catLocale=`basename ${cat%.*}` 35 | msgfmt -o "${catLocale}.mo" "$cat" 36 | 37 | installPath="$DIR/../contents/locale/${catLocale}/LC_MESSAGES/${projectName}.mo" 38 | 39 | echo "[build] Install to ${installPath}" 40 | mkdir -p "$(dirname "$installPath")" 41 | mv "${catLocale}.mo" "${installPath}" 42 | done 43 | 44 | echo "[build] Done building messages" 45 | 46 | if [ "$1" = "--restartplasma" ]; then 47 | echo "[build] Restarting plasmashell" 48 | killall plasmashell 49 | kstart5 plasmashell 50 | echo "[build] Done restarting plasmashell" 51 | else 52 | echo "[build] (re)install the plasmoid and restart plasmashell to test." 53 | fi 54 | -------------------------------------------------------------------------------- /package/contents/config/main.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 1000 11 | 12 | 13 | 60 14 | 15 | 16 | 250 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | [] 30 | 31 | 32 | 33 | [] 34 | 35 | 36 | 37 | 66 | [] 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /package/translate/install: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Version: 3 3 | 4 | # TODO: Call a CMakeLists.txt ... 5 | # OR just find out what env variables it looks at to determine the "/usr/share" dir. 6 | # TODO: check if run as root, and if so, install to /usr/share instead of ~/.local/share 7 | 8 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 9 | plasmoidName=`kreadconfig5 --file="$DIR/../metadata.desktop" --group="Desktop Entry" --key="X-KDE-PluginInfo-Name"` 10 | website=`kreadconfig5 --file="$DIR/../metadata.desktop" --group="Desktop Entry" --key="X-KDE-PluginInfo-Website"` 11 | bugAddress="$website" 12 | packageRoot=".." # Root of translatable sources 13 | projectName="plasma_applet_${plasmoidName}" # project name 14 | 15 | #--- 16 | if [ -z "$plasmoidName" ]; then 17 | echo "[install] Error: Couldn't read plasmoidName." 18 | exit 19 | fi 20 | 21 | if [ -z "$(which msgfmt)" ]; then 22 | echo "[install] Error: msgfmt command not found. Need to install gettext" 23 | echo "[install] Running 'sudo apt install gettext'" 24 | sudo apt install gettext 25 | echo "[install] gettext installation should be finished. Going back to installing translations." 26 | fi 27 | 28 | #--- 29 | echo "[install] Installing messages" 30 | 31 | catalogs=`find . -name '*.po'` 32 | for cat in $catalogs; do 33 | echo "$cat" 34 | catLocale=`basename ${cat%.*}` 35 | msgfmt -o "${catLocale}.mo" "$cat" 36 | 37 | # installPath="/usr/share/locale/${catLocale}/LC_MESSAGES/${projectName}.mo" 38 | installPath="${HOME}/.local/share/locale/${catLocale}/LC_MESSAGES/${projectName}.mo" 39 | 40 | mkdir -p "$(dirname "$installPath")" 41 | 42 | echo "[install] Install to ${installPath}" 43 | 44 | # sudo mv "${catLocale}.mo" "${installPath}" 45 | # sudo chown root "${installPath}" 46 | mv "${catLocale}.mo" "${installPath}" 47 | done 48 | 49 | echo "[install] Done installing messages (will take effect after restarting plasmashell)" 50 | 51 | echo "[install] Restarting plasmashell" 52 | killall plasmashell 53 | kstart5 plasmashell 54 | echo "[install] Done restarting plasmashell" 55 | -------------------------------------------------------------------------------- /package/contents/ui/JsonTableSensor.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Controls 1.0 3 | import QtQuick.Layouts 1.0 4 | 5 | TableViewColumn { 6 | id: tableViewColumn 7 | 8 | movable: false 9 | 10 | delegate: ComboBox { 11 | id: comboBox 12 | model: sensorDetector.model 13 | textRole: 'name' 14 | 15 | function hasKey() { 16 | return (typeof tableView.model[styleData.row] !== "undefined" 17 | && typeof tableView.model[styleData.row][tableViewColumn.role] !== "undefined" 18 | ) 19 | } 20 | 21 | readonly property var cellValue: { 22 | // console.log('Sensor.cellValue', hasKey(), tableView.model[styleData.row][tableViewColumn.role]) 23 | if (hasKey()) { 24 | return tableView.model[styleData.row][tableViewColumn.role] 25 | } else { 26 | return [] 27 | } 28 | } 29 | 30 | implicitWidth: 300 * units.devicePixelRatio 31 | 32 | function selectCurrentItem() { 33 | if (count > 0 && cellValue && cellValue.length >= 1) { 34 | var i = comboBox.find(cellValue[0]) 35 | if (i >= 0) { 36 | comboBox.currentIndex = i 37 | } 38 | } 39 | } 40 | 41 | // Note: sensorDetector.model is not populated during Component.onCompleted 42 | // Note: sensorDetector.modelChanged is fired with an empty list [] 43 | onModelChanged: { 44 | // console.log('sensorDetector.onModelChanged', sensorDetector.model) 45 | selectCurrentItem() 46 | } 47 | Component.onCompleted: { 48 | // console.log('ComboBox.onCompleted', sensorDetector.model) 49 | selectCurrentItem() 50 | } 51 | onCellValueChanged: { 52 | // console.log('ComboBox.onCellValueChanged', cellValue) 53 | selectCurrentItem() 54 | } 55 | 56 | onCurrentIndexChanged: { 57 | if (currentIndex <= 0) { 58 | return // skip 59 | } else if (cellValue && cellValue.length >= 1 && currentText == cellValue[0]) { 60 | return // skip 61 | } else { 62 | tableView.model[styleData.row][tableViewColumn.role] = [currentText] 63 | tableView.cellChanged(styleData.row, styleData.column, tableViewColumn.role) 64 | } 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Changelog.md: -------------------------------------------------------------------------------- 1 | ## v4 - December 2 2019 2 | 3 | * Fix bug when leaving "settings" config tab caused plasma to freeze. 4 | * Updated Dutch translation by @Vistaus (Pull Request #21) 5 | 6 | ## v3 - August 17 2019 7 | 8 | * Add a new config tab for tweaking the update interval (Issue #6) 9 | * Ignore tun0 and tap0 network interfaces (Issue #9) 10 | * Add ScrollView so we can show extra sensors. Each column scrolls independantly, but alway show cpu/ram/swap. (Issue #10) 11 | * If a disk has a label, show it in brackets (Issue #11) 12 | * If sensor units is in Volts, round to 2 decimal places (Issue #12) 13 | * Add ability to toggle pausing sensor updates using middle click. 14 | * Change the current default color for custom sensors from #000 to #888 15 | * Show sensor name by default for custom sensors. 16 | * Show a graph for each cpu core when more than 4 cores. 17 | * Disconnect sensors in PartitionUsageBar when they're removed. 18 | * Add a cpu based QML Canvas implementation instead of using KDE's hardware accelerated graphs which don't always work in a dashboard window for some reason. 19 | * Make sure the hovered line is aligned to a data point. 20 | * Show applet version in sensors page. 21 | * Implement a network toggle config page (Issue #8) 22 | * Add a text blurb and config button for new users (Issue #16) 23 | 24 | ## v2 - January 28 2019 25 | 26 | * Use multiple columns in the CPU legend to properly support >= 7 cpus. Displaying a max of 4 CPUs per column in the legend. 27 | * Remove tooltip delay before appearing. 28 | * Support opening the dashboard using a keyboard shortcut. 29 | * Add Dutch translations by @Vistaus (Pull Request #2) 30 | * Cleanup excess logging. 31 | 32 | ## v1 - November 15 2018 33 | 34 | * Display CPU/RAM usage over time. 35 | * Display Ethernet/Wifi/Disk I/O over time. 36 | * Display used storage for each Disk/Partition. 37 | * Panel "icon" is an animated version of the KSysGuard icon that shows your current CPU usage. 38 | * Uses the fullscreen popup window from the Application Dashboard widget. 39 | * Can quickly setup lm_sensors temp graphs and fan graphs in the right most column. 40 | -------------------------------------------------------------------------------- /package/contents/ui/NetworkListDetector.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | 3 | QtObject { 4 | id: networkListDetector 5 | 6 | property var networkModel: [] 7 | 8 | readonly property var ignoredNetworks: plasmoid.configuration.ignoredNetworks 9 | onIgnoredNetworksChanged: { 10 | updateNetworkModel() 11 | } 12 | 13 | property Connections sensorConnection: Connections { 14 | target: sensorData 15 | onNetworkSensorListChanged: { 16 | networkListDetector.updateNetworkModel() 17 | } 18 | } 19 | 20 | function updateNetworkModel() { 21 | // [ 22 | // { 23 | // "label": "Network", 24 | // "icon": "network-wired", 25 | // "interfaceName": "enp1s0" 26 | // }, 27 | // { 28 | // "label": "WiFi", 29 | // "icon": "network-wireless", 30 | // "interfaceName": "wlp1s0" 31 | // } 32 | // ] 33 | var newNetworkModel = [] 34 | for (var i = 0; i < sensorData.networkSensorList.length; i++) { 35 | var networkName = sensorData.networkSensorList[i] 36 | 37 | // SystemD network naming scheme: 38 | // https://www.freedesktop.org/wiki/Software/systemd/PredictableNetworkInterfaceNames/ 39 | // Eg: wlp5s6 40 | // First two letters are the hardware type. 41 | // p5 = Port 5 42 | // s6 = Slot 6 43 | 44 | // Keep this in sync with ConfigNetworks.qml 45 | if (networkName == 'lo' // Ignore loopback device 46 | || networkName.match(/^docker(\d+)/) // Ignore docker networks 47 | || networkName.match(/^(tun|tap)(\d+)/) // Ingore tun/tap interfaces 48 | ) { 49 | continue 50 | } 51 | 52 | if (ignoredNetworks.indexOf(networkName) >= 0) { 53 | continue 54 | } 55 | 56 | var newNetwork = {} 57 | newNetwork.interfaceName = networkName 58 | 59 | // First two letters are 60 | if (networkName.match(/^wl/)) { // Wireless 61 | newNetwork.label = i18n("Wi-Fi") 62 | newNetwork.icon = "network-wireless" 63 | } else { // Eg: en (Ethernet) 64 | newNetwork.label = i18n("Network") 65 | newNetwork.icon = "network-wired" 66 | } 67 | newNetworkModel.push(newNetwork) 68 | } 69 | 70 | networkModel = newNetworkModel 71 | 72 | // console.log(JSON.stringify(networkModel, null, ' ')) 73 | } 74 | } 75 | 76 | -------------------------------------------------------------------------------- /package/contents/ui/config/ConfigNetworks.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Controls 1.0 3 | import QtQuick.Layouts 1.0 4 | 5 | import ".." 6 | import "../lib" 7 | 8 | ConfigPage { 9 | 10 | signal configurationChanged() 11 | 12 | SensorDetector { 13 | id: sensorDetector 14 | 15 | property var networkSensorList: [] 16 | 17 | onModelChanged: { 18 | var newNetworkList = [] 19 | for (var i = 0; i < model.count; i++) { 20 | var sensor = model.get(i) 21 | var match = sensor.name.match(/^network\/interfaces\/(\w+)\//) 22 | if (match) { 23 | var networkName = match[1] 24 | if (newNetworkList.indexOf(networkName) === -1) { 25 | // Add if not seen before 26 | newNetworkList.push(networkName) 27 | } 28 | } 29 | } 30 | networkSensorList = newNetworkList 31 | networkSensorListChanged() 32 | } 33 | } 34 | 35 | Repeater { 36 | model: sensorDetector.networkSensorList 37 | 38 | CheckBox { 39 | readonly property string networkName: modelData 40 | readonly property bool ignoredByDefault: { 41 | // Keep this in sync with NetworkListDetector.qml 42 | return networkName == 'lo' // Ignore loopback device 43 | || networkName.match(/^docker(\d+)/) // Ignore docker networks 44 | || networkName.match(/^(tun|tap)(\d+)/) // Ingore tun/tap interfaces 45 | } 46 | 47 | text: networkName 48 | checked: plasmoid.configuration.ignoredNetworks.indexOf(networkName) == -1 && !ignoredByDefault 49 | enabled: !ignoredByDefault 50 | 51 | onClicked: { 52 | var ignoredNetworks = plasmoid.configuration.ignoredNetworks.slice(0) // copy() 53 | if (checked) { 54 | // Checking, and thus removing from the ignoredNetworks 55 | var i = ignoredNetworks.indexOf(networkName) 56 | ignoredNetworks.splice(i, 1) 57 | plasmoid.configuration.ignoredNetworks = ignoredNetworks 58 | } else { 59 | // Unchecking, and thus adding to the ignoredNetworks 60 | ignoredNetworks.push(networkName) 61 | plasmoid.configuration.ignoredNetworks = ignoredNetworks 62 | } 63 | // To modify a StringList we need to manually trigger configurationChanged. 64 | configurationChanged() 65 | } 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /package/contents/icons/amd-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 19 | 20 | 22 | image/svg+xml 23 | 25 | 26 | 27 | 28 | 48 | 50 | 57 | 58 | 64 | 70 | 71 | -------------------------------------------------------------------------------- /package/contents/ui/DiskUsageBar.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Layouts 1.1 3 | import QtQuick.Window 2.1 4 | 5 | Item { 6 | id: diskUsageBar 7 | Layout.fillWidth: true 8 | Layout.preferredHeight: 16 * units.devicePixelRatio 9 | 10 | property var partitionPaths: [] 11 | property int minimumPartitionWidth: 30 * units.devicePixelRatio 12 | 13 | Row { 14 | id: row 15 | anchors.fill: parent 16 | 17 | spacing: 4 * units.devicePixelRatio 18 | 19 | Repeater { 20 | id: repeater 21 | model: partitionPaths 22 | 23 | PartitionUsageBar { 24 | partitionPath: modelData 25 | onTotalspaceChanged: diskUsageBar.updateDiskSize() 26 | 27 | height: parent.height 28 | } 29 | } 30 | } 31 | 32 | property double totalspace: 0 33 | function updateDiskSize() { 34 | // console.log('updateDiskSize', partitionPaths) 35 | var diskTotalspace = 0 36 | for (var i = 0; i < repeater.count; i++) { 37 | var item = repeater.itemAt(i) 38 | diskTotalspace += item.totalspace 39 | // console.log('\ttotalspace', diskTotalspace, 'calc', item.usedspace, item.freespace, item.totalspace) 40 | } 41 | totalspace = diskTotalspace 42 | // updatePartitionSizes() 43 | } 44 | 45 | 46 | onTotalspaceChanged: updatePartitionSizes() 47 | onWidthChanged: updatePartitionSizes() 48 | function updatePartitionSizes() { 49 | if (diskUsageBar.totalspace > 0) { 50 | var contentWidth = width - Math.max(0, (repeater.count - 1) * spacing) 51 | // var extraWidth = Math.max(0, contentWidth - Math.max(0, (repeater.count - 1) * minimumPartitionWidth)) 52 | var extraWidth = contentWidth - Math.max(0, (repeater.count) * minimumPartitionWidth) 53 | // console.log('updatePartitionSizes') 54 | // console.log('\t', 'contentWidth', contentWidth) 55 | // console.log('\t', 'extraWidth', extraWidth) 56 | for (var i = 0; i < repeater.count; i++) { 57 | var item = repeater.itemAt(i) 58 | 59 | var scaledExtraWidth = extraWidth * (item.totalspace / diskUsageBar.totalspace) 60 | var preferredWidth = minimumPartitionWidth + scaledExtraWidth 61 | // console.log('\t', extraWidth, scaledExtraWidth, preferredWidth, minimumPartitionWidth) 62 | 63 | item.width = preferredWidth 64 | // item.implicitWidth = preferredWidth 65 | // item.Layout.preferredWidth = preferredWidth 66 | // item.Layout.preferredWidth = Qt.binding(function(){ 67 | // return diskUsageBar.width * (item.totalspace / diskUsageBar.totalspace) 68 | // }) 69 | } 70 | } 71 | } 72 | 73 | 74 | // Timer { 75 | // running: true 76 | // // interval: 2000 77 | // repeat: true 78 | // interval: 1000 79 | // onTriggered: updatePartitionSizes() 80 | // } 81 | } 82 | -------------------------------------------------------------------------------- /package/contents/ui/ListBlockDevices.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import org.kde.plasma.core 2.0 as PlasmaCore 3 | 4 | QtObject { 5 | id: lsblk 6 | property string updateCommand: "lsblk --output-all --json" 7 | property var value: ({}) // Empty Map 8 | 9 | function update() { 10 | execUtil.exec(updateCommand, commandDone) 11 | } 12 | 13 | function commandDone(cmd, exitCode, exitStatus, stdout, stderr) { 14 | // console.log('commandDone', exitCode, exitStatus, typeof exitCode) 15 | // console.log('\tstdout:', stdout) 16 | // console.log('\tstderr:', stderr) 17 | if (exitCode == 0) { // Sucess 18 | var newValue = JSON.parse(stdout) 19 | lsblk.value = newValue 20 | } 21 | updateDiskModel() 22 | } 23 | 24 | function updateDiskModel() { 25 | if (!(value && value.blockdevices)) 26 | return; 27 | 28 | // Sort names so it's: [sda, sdb, ...] 29 | value.blockdevices.sort(function(a,b) { 30 | return a.name - b.name 31 | }) 32 | 33 | var newDiskModel = [] 34 | for (var i = 0; i < value.blockdevices.length; i++) { 35 | var blockDevice = value.blockdevices[i] 36 | if (blockDevice.type == "disk") { 37 | var newDisk = {} 38 | 39 | var diskLabel 40 | if (blockDevice.label) { 41 | diskLabel = i18nc("diskName diskLabel", "%1 (%2)", blockDevice.name, blockDevice.label) 42 | } else { 43 | diskLabel = '' + blockDevice.name 44 | } 45 | newDisk.label = i18n("Disk: %1", diskLabel) 46 | 47 | newDisk.sublabel = i18nc("diskModel sizeGb", "%1 %2", blockDevice.model, blockDevice.size) 48 | 49 | // "sda_(8:0)" (used by the systemmonitor datasource for disk i/o speed) 50 | newDisk.partitionId = blockDevice.name + "_(" + blockDevice['maj:min'] + ")" 51 | 52 | newDisk.partitionPaths = [] 53 | if (blockDevice.children) { 54 | for (var j = 0; j < blockDevice.children.length; j++) { 55 | var deviceChild = blockDevice.children[j] 56 | 57 | if (deviceChild.fstype == "swap") { 58 | continue 59 | } 60 | 61 | if (deviceChild.mountpoint) { 62 | newDisk.partitionPaths.push(deviceChild.mountpoint) 63 | } 64 | } 65 | } 66 | 67 | newDiskModel.push(newDisk) 68 | } 69 | } 70 | 71 | // console.log("newDiskModel", JSON.stringify(newDiskModel, null, "\t")) 72 | config.diskModel = newDiskModel 73 | } 74 | 75 | property var hpSource: PlasmaCore.DataSource { 76 | id: hpSource 77 | engine: "hotplug" 78 | // connectedSources: sources 79 | interval: 0 80 | 81 | onSourceAdded: { 82 | // disconnectSource(source) 83 | // connectSource(source) 84 | // console.log('hotplug.onSourceAdded', source) 85 | // lsblk.update() 86 | } 87 | onSourceRemoved: { 88 | // disconnectSource(source) 89 | // console.log('hotplug.onSourceRemoved', source) 90 | // lsblk.update() 91 | } 92 | } 93 | 94 | Component.onCompleted: { 95 | update() 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /package/translate/plasmoidlocaletest: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Version 6 3 | # Requires plasmoidviewer v5.13.0 4 | 5 | function checkIfLangInstalled { 6 | if [ -x "$(command -v dpkg)" ]; then 7 | dpkg -l ${1} >/dev/null 2>&1 || ( \ 8 | echo -e "${1} not installed.\nInstalling now before continuing.\n" \ 9 | ; sudo apt install ${1} \ 10 | ) || ( \ 11 | echo -e "\nError trying to install ${1}\nPlease run 'sudo apt install ${1}'\n" \ 12 | ; exit 1 \ 13 | ) 14 | elif [ -x "$(command -v pacman)" ]; then 15 | # TODO: run `locale -a` and check if the locale is enabled. 16 | if false; then 17 | # https://wiki.archlinux.org/index.php/Locale 18 | # Uncomment the locale in /etc/locale.gen 19 | # Then run `locale-gen` 20 | echo -e "\nPlease install this locale in System Settings first.\n" 21 | exit 1 22 | else 23 | echo "" 24 | fi 25 | else 26 | echo -e "\nPackage manager not recognized. If the widget is not translated, please install the package '${1}'\n" 27 | fi 28 | } 29 | 30 | langInput="${1}" 31 | lang="" 32 | languagePack="" 33 | 34 | if [[ "$langInput" =~ ":" ]]; then # String contains a colon so assume it's a locale code. 35 | lang="${langInput}" 36 | IFS=: read -r l1 l2 <<< "${lang}" 37 | languagePack="language-pack-${l2}" 38 | fi 39 | 40 | declare -a langArr=( 41 | "ar_EG:ar:Arabic (Egypt)" 42 | "bg_BG:bg:Bulgarian (Bulgaria)" 43 | "da_DK:da:Danish (Denmark)" 44 | "de_DE:de:German (Germany)" 45 | "el_GR:el:Greek (Greece)" 46 | "es_MX:es:Spanish (Mexico)" 47 | "fr_CA:fr:French (Canada)" 48 | "hr_HR:hr:Croatian (Croatia)" 49 | "id_ID:id:Indonesian (Indonesia)" 50 | "ko_KR:ko:Korean (South Korea)" 51 | "nl_NL:nl:Dutch (Netherlands)" 52 | "pl_PL:pl:Polish (Poland)" 53 | "pt_BR:pt:Portuguese (Brazil)" 54 | "ru_RU:ru:Russian (Russia)" 55 | "tr_TR:tr:Turkish (Turkey)" 56 | "uk_UA:uk:Ukrainian (Ukraine)" 57 | "zh_CN:zh:Chinese (China)" 58 | ) 59 | 60 | for i in "${langArr[@]}"; do 61 | IFS=: read -r l1 l2 l3 <<< "$i" 62 | if [ "$langInput" == "$l2" ]; then 63 | lang="${l1}:${l2}" 64 | languagePack="language-pack-${l2}" 65 | fi 66 | done 67 | 68 | if [ -z "$lang" ]; then 69 | echo "plasmoidlocaletest doesn't recognize the language '$lang'" 70 | echo "Eg:" 71 | scriptcmd='sh ./plasmoidlocaletest' 72 | for i in "${langArr[@]}"; do 73 | IFS=: read -r l1 l2 l3 <<< "$i" 74 | echo " ${scriptcmd} ${l2} | ${l3}" 75 | done 76 | echo "" 77 | echo "Or use a the full locale code:" 78 | echo " ${scriptcmd} ar_EG:ar" 79 | exit 1 80 | fi 81 | 82 | IFS=: read -r l1 l2 <<< "${lang}" 83 | l1="${l1}.UTF-8" 84 | 85 | # Check if language is installed 86 | if [ ! -z "$languagePack" ]; then 87 | if [ "$lang" == "zh_CN:zh" ]; then languagePack="language-pack-zh-hans" 88 | fi 89 | 90 | checkIfLangInstalled "$languagePack" || exit 1 91 | fi 92 | 93 | 94 | echo "LANGUAGE=\"${lang}\"" 95 | echo "LANG=\"${l1}\"" 96 | 97 | scriptDir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 98 | packageDir="${scriptDir}/.." 99 | 100 | # Build local translations for plasmoidviewer 101 | sh "${scriptDir}/build" 102 | 103 | LANGUAGE="${lang}" LANG="${l1}" LC_TIME="${l1}" QML_DISABLE_DISK_CACHE=true plasmoidviewer -a "$packageDir" -l topedge -f horizontal -x 0 -y 0 104 | -------------------------------------------------------------------------------- /package/contents/ui/PartitionUsageBar.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Controls 2.0 // ToolTip 3 | import QtQuick.Layouts 1.1 4 | import QtQuick.Window 2.1 5 | 6 | SimpleProgressBar { 7 | id: partitionUsageBar 8 | 9 | MouseArea { 10 | id: mouseArea 11 | anchors.fill: parent 12 | acceptedButtons: Qt.NoButton 13 | hoverEnabled: true 14 | ToolTip { 15 | id: control 16 | visible: mouseArea.containsMouse 17 | text: i18n("%1
Size: %2
Used: %3
Free: %4", partitionPath, humanReadableBytes(totalspace), humanReadableBytes(usedspace), humanReadableBytes(freespace)) 18 | delay: 0 19 | } 20 | } 21 | 22 | property int labelPadding: 4 * units.devicePixelRatio 23 | 24 | property string partitionPath 25 | readonly property double usedspace: sensorData.getData('partitions' + partitionPath + '/usedspace') 26 | readonly property double freespace: sensorData.getData('partitions' + partitionPath + '/freespace') 27 | readonly property double filllevel: sensorData.getData('partitions' + partitionPath + '/filllevel') 28 | property double totalspace: 0 29 | readonly property bool isMounted: totalspace > 0 30 | 31 | property alias label: label.text 32 | property alias showLabel: label.visible 33 | 34 | Timer { 35 | id: totalspaceDebounce 36 | interval: 200 37 | onTriggered: { 38 | partitionUsageBar.totalspace = partitionUsageBar.usedspace + partitionUsageBar.freespace 39 | } 40 | } 41 | 42 | onUsedspaceChanged: { 43 | // console.log(partitionPath, 'usedspace', usedspace) 44 | totalspaceDebounce.restart() 45 | } 46 | onFreespaceChanged: { 47 | // console.log(partitionPath, 'freespace', freespace) 48 | totalspaceDebounce.restart() 49 | } 50 | // onFilllevelChanged: console.log(partitionPath, 'filllevel', filllevel) 51 | // onTotalspaceChanged: console.log(partitionPath, 'totalspace', totalspace) 52 | 53 | // value: usedspace / (usedspace+freespace) 54 | // maxValue: 1 55 | value: filllevel 56 | maxValue: 100 57 | 58 | opacity: isMounted ? 1 : 0.3 59 | 60 | function connectSource(sensor) { 61 | // console.log('connectedSources', sensor, sensorData.dataSource.connectedSources.indexOf(sensor)) 62 | if (sensorData.dataSource.connectedSources.indexOf(sensor) == -1) { 63 | // console.log('connectSource', sensor) 64 | sensorData.dataSource.connectSource(sensor) 65 | } 66 | } 67 | function disconnectSource(sensor) { 68 | // console.log('connectedSources', sensor, sensorData.dataSource.connectedSources.indexOf(sensor)) 69 | if (sensorData.dataSource.connectedSources.indexOf(sensor) >= 0) { 70 | // console.log('disconnectSource', sensor) 71 | sensorData.dataSource.disconnectSource(sensor) 72 | } 73 | } 74 | 75 | Text { 76 | id: label 77 | opacity: (partitionUsageBar.width >= (labelPadding + contentWidth + labelPadding)) ? 1 : 0 78 | text: partitionPath 79 | color: "#ffffff" 80 | font.pointSize: -1 81 | font.pixelSize: parent.height - labelPadding // Half padding top+bottom since font has extra spacing 82 | font.bold: true 83 | style: Text.Outline 84 | styleColor: "#00000088" 85 | anchors.verticalCenter: parent.verticalCenter 86 | anchors.left: parent.left 87 | anchors.leftMargin: labelPadding 88 | anchors.rightMargin: labelPadding 89 | } 90 | 91 | Connections { 92 | target: sensorData.dataSource 93 | 94 | function isPartitionSource(source) { 95 | var partitionRegExp = new RegExp("^partitions(/.*)/filllevel$") 96 | var match = source.match(partitionRegExp) 97 | return match 98 | } 99 | 100 | onSourceAdded: { 101 | var match = isPartitionSource(source) 102 | if (match) { 103 | if (match[1] == partitionPath) { 104 | partitionUsageBar.connectSource('partitions' + partitionPath + '/usedspace') 105 | partitionUsageBar.connectSource('partitions' + partitionPath + '/freespace') 106 | partitionUsageBar.connectSource('partitions' + partitionPath + '/filllevel') 107 | } 108 | } 109 | } 110 | onSourceRemoved: { 111 | var match = isPartitionSource(source) 112 | if (match) { 113 | if (match[1] == partitionPath) { 114 | partitionUsageBar.disconnectSource('partitions' + partitionPath + '/usedspace') 115 | partitionUsageBar.disconnectSource('partitions' + partitionPath + '/freespace') 116 | partitionUsageBar.disconnectSource('partitions' + partitionPath + '/filllevel') 117 | } 118 | } 119 | } 120 | } 121 | 122 | // Component.onCompleted: { 123 | // connectSource('partitions' + partitionPath + '/usedspace') 124 | // connectSource('partitions' + partitionPath + '/freespace') 125 | // connectSource('partitions' + partitionPath + '/filllevel') 126 | // } 127 | } 128 | -------------------------------------------------------------------------------- /package/contents/ui/config/ConfigSensors.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Controls 1.0 3 | import QtQuick.Layouts 1.0 4 | 5 | import ".." 6 | import "../lib" 7 | 8 | ColumnLayout { 9 | 10 | SensorDetector { 11 | id: sensorDetector 12 | } 13 | 14 | QtObject { 15 | id: sensorModel 16 | readonly property string configValue: plasmoid.configuration.sensorModel 17 | readonly property var valueObj: { 18 | try { 19 | return JSON.parse(configValue) 20 | } catch (err) { 21 | return "" 22 | } 23 | } 24 | function getter() { 25 | try { 26 | return JSON.parse(plasmoid.configuration.sensorModel) 27 | } catch (err) { 28 | return "" 29 | } 30 | } 31 | // readonly property string jsonValue: JSON.stringify(valueObj, null, ' ') 32 | } 33 | 34 | RowLayout { 35 | Flow { 36 | Layout.fillWidth: true 37 | 38 | Button { 39 | iconName: "edit-table-insert-row-below" 40 | text: i18n("Add Sensor") 41 | onClicked: tableView.addRow() 42 | } 43 | Button { 44 | iconName: "edit-table-insert-row-below" 45 | text: i18n("Add Temps") 46 | onClicked: tableView.addAllTemps() 47 | } 48 | Button { 49 | iconName: "edit-table-insert-row-below" 50 | text: i18n("Add Fans") 51 | onClicked: tableView.addAllFans() 52 | } 53 | Button { 54 | iconName: "edit-table-insert-row-below" 55 | text: i18n("Add all lm_sensors") 56 | onClicked: tableView.addAllLmSensors() 57 | } 58 | } 59 | 60 | Loader { 61 | id: appletVersionLoader 62 | source: "../lib/AppletVersion.qml" 63 | Layout.alignment: Qt.AlignTop | Qt.AlignRight 64 | } 65 | } 66 | 67 | JsonTableView { 68 | id: tableView 69 | Layout.fillWidth: true 70 | Layout.fillHeight: true 71 | 72 | function updateConfigValue() { 73 | sensorModel.valueObjChanged() 74 | var newValue = JSON.stringify(tableView.model, null, ' ') 75 | plasmoid.configuration.sensorModel = newValue 76 | } 77 | 78 | function indexOfSensor(sensorName) { 79 | for (var rowIndex = 0; rowIndex < tableView.model.length; rowIndex++) { 80 | var row = tableView.model[rowIndex] 81 | if (typeof row.sensors !== "undefined") { 82 | for (var i = 0; i < row.sensors.length; i++) { 83 | if (row.sensors[i] == sensorName) { 84 | return rowIndex 85 | } 86 | } 87 | } 88 | } 89 | return -1 90 | } 91 | 92 | function hasRowWithSensor(sensorName) { 93 | return indexOfSensor(sensorName) >= 0 94 | } 95 | 96 | function addAllSensors(predicate) { 97 | var rowAdded = false 98 | var sensors = sensorDetector.model 99 | for (var i = 0; i < sensors.count; i++) { 100 | var sensor = sensors.get(i) 101 | var shouldAdd = predicate(sensor.name) 102 | if (shouldAdd) { 103 | console.log('sensorAdded', i, sensor.name) 104 | var newLength = tableView.model.push({ 105 | sensors: [sensor.name] 106 | }) 107 | var newIndex = newLength - 1 108 | console.log('\t', newIndex, JSON.stringify(tableView.model[newIndex])) 109 | // tableView.rowAdded(newIndex) // it only calls updateConfigValue() 110 | rowAdded = true 111 | } 112 | } 113 | if (rowAdded) { 114 | tableView.modelChanged() 115 | updateConfigValue() 116 | } 117 | } 118 | 119 | function addAllRegex(regex) { 120 | tableView.addAllSensors(function(sensorName){ 121 | return sensorName.match(regex) 122 | && !tableView.hasRowWithSensor(sensorName) 123 | }) 124 | } 125 | 126 | function addAllTemps() { 127 | tableView.addAllRegex(/^lmsensors\/.+\/temp\d+$/) 128 | } 129 | 130 | function addAllFans() { 131 | tableView.addAllRegex(/^lmsensors\/.+\/fan\d+$/) 132 | } 133 | 134 | function addAllLmSensors() { 135 | tableView.addAllRegex(/^lmsensors\//) 136 | } 137 | 138 | onCellChanged: { 139 | updateConfigValue() 140 | resizeColumnsToContents() 141 | } 142 | onRowAdded: updateConfigValue() 143 | onRowRemoved: updateConfigValue() 144 | 145 | Component.onCompleted: { 146 | tableView.model = sensorModel.getter() 147 | resizeColumnsToContents() 148 | } 149 | 150 | TableViewColumn { 151 | delegate: Button { 152 | iconName: "delete" 153 | onClicked: tableView.removeRow(styleData.row) 154 | } 155 | } 156 | 157 | JsonTableSensor { 158 | role: "sensors" 159 | title: i18n("Sensor") 160 | } 161 | JsonTableString { 162 | role: "label" 163 | title: i18n("Label") 164 | } 165 | JsonTableString { 166 | role: "sublabel" 167 | title: i18n("SubLabel") 168 | } 169 | JsonTableStringList { 170 | role: "colors" 171 | title: i18n("Color") 172 | placeholderText: "#000000" 173 | } 174 | JsonTableString { 175 | role: "icon" 176 | title: i18n("Icon") 177 | } 178 | JsonTableString { 179 | role: "units" 180 | title: i18n("Units") 181 | } 182 | JsonTableInt { 183 | role: "defaultMax" 184 | title: i18n("MaxY") 185 | } 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /package/translate/template.pot: -------------------------------------------------------------------------------- 1 | # Translation of sysmonitordash in LANGUAGE 2 | # Copyright (C) 2019 3 | # This file is distributed under the same license as the sysmonitordash package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: sysmonitordash \n" 10 | "Report-Msgid-Bugs-To: https://github.com/Zren/plasma-applet-sysmonitordash\n" 11 | "POT-Creation-Date: 2019-12-02 17:10-0500\n" 12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 13 | "Last-Translator: FULL NAME \n" 14 | "Language-Team: LANGUAGE \n" 15 | "Language: \n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=CHARSET\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | 20 | #: ../contents/config/config.qml 21 | msgid "Sensors" 22 | msgstr "" 23 | 24 | #: ../contents/config/config.qml 25 | msgid "Networks" 26 | msgstr "" 27 | 28 | #: ../contents/config/config.qml 29 | msgid "Settings" 30 | msgstr "" 31 | 32 | #: ../contents/ui/config/ConfigSensors.qml 33 | msgid "Add Sensor" 34 | msgstr "" 35 | 36 | #: ../contents/ui/config/ConfigSensors.qml 37 | msgid "Add Temps" 38 | msgstr "" 39 | 40 | #: ../contents/ui/config/ConfigSensors.qml 41 | msgid "Add Fans" 42 | msgstr "" 43 | 44 | #: ../contents/ui/config/ConfigSensors.qml 45 | msgid "Add all lm_sensors" 46 | msgstr "" 47 | 48 | #: ../contents/ui/config/ConfigSensors.qml 49 | msgid "Sensor" 50 | msgstr "" 51 | 52 | #: ../contents/ui/config/ConfigSensors.qml 53 | msgid "Label" 54 | msgstr "" 55 | 56 | #: ../contents/ui/config/ConfigSensors.qml 57 | msgid "SubLabel" 58 | msgstr "" 59 | 60 | #: ../contents/ui/config/ConfigSensors.qml 61 | msgid "Color" 62 | msgstr "" 63 | 64 | #: ../contents/ui/config/ConfigSensors.qml 65 | msgid "Icon" 66 | msgstr "" 67 | 68 | #: ../contents/ui/config/ConfigSensors.qml 69 | msgid "Units" 70 | msgstr "" 71 | 72 | #: ../contents/ui/config/ConfigSensors.qml 73 | msgid "MaxY" 74 | msgstr "" 75 | 76 | #: ../contents/ui/config/ConfigSettings.qml 77 | msgid "Dashboard Sensors" 78 | msgstr "" 79 | 80 | #: ../contents/ui/config/ConfigSettings.qml 81 | msgid "Update Every:" 82 | msgstr "" 83 | 84 | #: ../contents/ui/config/ConfigSettings.qml 85 | msgid "ms" 86 | msgstr "" 87 | 88 | #: ../contents/ui/config/ConfigSettings.qml 89 | msgid "Visible Duration:" 90 | msgstr "" 91 | 92 | #: ../contents/ui/config/ConfigSettings.qml 93 | msgid "sec" 94 | msgstr "" 95 | 96 | #: ../contents/ui/config/ConfigSettings.qml 97 | msgid "Panel Icon" 98 | msgstr "" 99 | 100 | #: ../contents/ui/DashView.qml 101 | msgid "CPU" 102 | msgstr "" 103 | 104 | #: ../contents/ui/DashView.qml 105 | msgid "Cached" 106 | msgstr "" 107 | 108 | #: ../contents/ui/DashView.qml 109 | msgid "Buffered" 110 | msgstr "" 111 | 112 | #: ../contents/ui/DashView.qml 113 | msgid "Apps" 114 | msgstr "" 115 | 116 | #: ../contents/ui/DashView.qml 117 | msgid "Free" 118 | msgstr "" 119 | 120 | #: ../contents/ui/DashView.qml 121 | msgid "RAM" 122 | msgstr "" 123 | 124 | #: ../contents/ui/DashView.qml 125 | msgid "Used" 126 | msgstr "" 127 | 128 | #: ../contents/ui/DashView.qml 129 | msgid "Swap" 130 | msgstr "" 131 | 132 | #: ../contents/ui/DiskIOGraph.qml 133 | msgid "Read" 134 | msgstr "" 135 | 136 | #: ../contents/ui/DiskIOGraph.qml 137 | msgid "Write" 138 | msgstr "" 139 | 140 | #: ../contents/ui/lib/AppletVersion.qml 141 | msgid "Version: %1" 142 | msgstr "" 143 | 144 | #: ../contents/ui/ListBlockDevices.qml 145 | msgctxt "diskName diskLabel" 146 | msgid "%1 (%2)" 147 | msgstr "" 148 | 149 | #: ../contents/ui/ListBlockDevices.qml 150 | msgid "Disk: %1" 151 | msgstr "" 152 | 153 | #: ../contents/ui/ListBlockDevices.qml 154 | msgctxt "diskModel sizeGb" 155 | msgid "%1 %2" 156 | msgstr "" 157 | 158 | #: ../contents/ui/main.qml 159 | msgid "Start Task Manager" 160 | msgstr "" 161 | 162 | #: ../contents/ui/NetworkIOGraph.qml 163 | msgid "Download" 164 | msgstr "" 165 | 166 | #: ../contents/ui/NetworkIOGraph.qml 167 | msgid "Upload" 168 | msgstr "" 169 | 170 | #: ../contents/ui/NetworkIOGraph.qml ../contents/ui/NetworkListDetector.qml 171 | msgid "Network" 172 | msgstr "" 173 | 174 | #: ../contents/ui/NetworkListDetector.qml 175 | msgid "Wi-Fi" 176 | msgstr "" 177 | 178 | #: ../contents/ui/NewUserSplash.qml 179 | msgid "You can hide disks and networks in the config. You can also monitor temperature and fan speed sensors if you have lm-sensors installed." 180 | msgstr "" 181 | 182 | #: ../contents/ui/NewUserSplash.qml 183 | msgid "Configure Sensors" 184 | msgstr "" 185 | 186 | #: ../contents/ui/PartitionUsageBar.qml 187 | msgid "%1
Size: %2
Used: %3
Free: %4" 188 | msgstr "" 189 | 190 | #: ../contents/ui/SensorGraph.qml 191 | msgctxt "%1 is data value, %2 is unit datatype" 192 | msgid "%1 %2" 193 | msgstr "" 194 | 195 | #: ../contents/ui/SensorPresets.qml 196 | msgid "Fan" 197 | msgstr "" 198 | 199 | #: ../contents/ui/SensorPresets.qml 200 | msgid "Temp" 201 | msgstr "" 202 | 203 | #: ../contents/ui/SensorPresets.qml 204 | msgid "GPU" 205 | msgstr "" 206 | -------------------------------------------------------------------------------- /package/contents/ui/main.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Window 2.7 3 | import QtQuick.Layouts 1.1 4 | import org.kde.plasma.plasmoid 2.0 5 | import org.kde.plasma.core 2.0 as PlasmaCore 6 | 7 | import org.kde.kquickcontrolsaddons 2.0 as KQuickAddons 8 | 9 | import "lib" 10 | 11 | Item { 12 | id: main 13 | 14 | DeviceData { 15 | id: deviceData 16 | } 17 | 18 | SensorData { 19 | id: sensorData 20 | interval: config.sensorInterval 21 | } 22 | 23 | QtObject { 24 | id: config 25 | property int sensorInterval: plasmoid.configuration.dashUpdateInterval 26 | property int visibleDuration: plasmoid.configuration.dashVisibleDuration * 1000 27 | property int iconSensorInterval: plasmoid.configuration.iconUpdateInterval 28 | // property int iconVisibleDuration: iconSensorInterval * 5 29 | 30 | property var diskModel: [] 31 | property var networkModel: [] 32 | property var sensorModel: [] 33 | } 34 | 35 | SensorPresets { 36 | id: sensorPresets 37 | } 38 | 39 | function initJsonObjArr(configKey) { 40 | // `plasmoid.configuration` is a QQmlPropertyMap 41 | // https://github.com/KDE/plasma-framework/blob/master/src/scriptengines/qml/plasmoid/appletinterface.cpp#L161 42 | // https://github.com/KDE/kdeclarative/blob/master/src/kdeclarative/configpropertymap.h 43 | plasmoid.configuration.valueChanged.connect(function(key, value){ 44 | if (key == configKey) { 45 | config[configKey] = JSON.parse(plasmoid.configuration[configKey]) 46 | } 47 | }) 48 | config[configKey] = JSON.parse(plasmoid.configuration[configKey]) 49 | } 50 | 51 | 52 | Plasmoid.preferredRepresentation: Plasmoid.compactRepresentation 53 | 54 | ExecUtil { 55 | id: execUtil 56 | } 57 | 58 | ListBlockDevices { 59 | id: lsblk 60 | } 61 | 62 | NetworkListDetector { 63 | id: networkListDetector 64 | onNetworkModelChanged: { 65 | config.networkModel = networkModel 66 | } 67 | } 68 | 69 | function action_openTaskManager() { 70 | execUtil.exec("ksysguard") 71 | } 72 | 73 | Component.onCompleted: { 74 | plasmoid.setAction("openTaskManager", i18n("Start Task Manager"), "utilities-system-monitor"); 75 | 76 | // initJsonObjArr('diskModel') 77 | // initJsonObjArr('networkModel') 78 | initJsonObjArr('sensorModel') 79 | 80 | // plasmoid.action("configure").trigger() 81 | } 82 | 83 | // NOTE: taken from redshift plasmoid (who took in from colorPicker) 84 | // This prevents the popup from actually opening, needs to be queued. 85 | Timer { 86 | id: delayedUnexpandTimer 87 | interval: 0 88 | onTriggered: { 89 | plasmoid.expanded = false 90 | } 91 | } 92 | function toggleDialog() { 93 | delayedUnexpandTimer.start() 94 | dashView.toggle() 95 | } 96 | Plasmoid.onActivated: main.toggleDialog() 97 | 98 | DashView { 99 | id: dashView 100 | } 101 | Plasmoid.compactRepresentation: MouseArea { 102 | anchors.fill: parent 103 | acceptedButtons: Qt.LeftButton | Qt.MiddleButton 104 | onClicked: { 105 | if (mouse.button == Qt.LeftButton) { 106 | plasmoid.activated() 107 | } else { 108 | main.toggleSensors() 109 | } 110 | } 111 | 112 | // PlasmaCore.IconItem { 113 | // anchors.fill: parent 114 | // source: "utilities-system-monitor" 115 | // } 116 | 117 | Rectangle { 118 | anchors.fill: parent 119 | radius: 2 120 | gradient: Gradient { 121 | GradientStop { position: 0.0; color: "#424649" } 122 | GradientStop { position: 1.0; color: "#2a2c2f" } 123 | } 124 | 125 | KQuickAddons.Plotter { 126 | id: compactPlotter 127 | anchors.fill: parent 128 | sampleSize: 5 129 | rangeMin: 0 130 | rangeMax: 100 131 | autoRange: false 132 | 133 | dataSets: [ 134 | KQuickAddons.PlotData { 135 | color: "#77b76b" 136 | } 137 | ] 138 | 139 | Component.onCompleted: { 140 | // addSample([5]) 141 | // addSample([8]) 142 | // addSample([4]) 143 | // addSample([6]) 144 | // addSample([4]) 145 | addSample([0]) 146 | addSample([0]) 147 | } 148 | 149 | Timer { 150 | interval: config.iconSensorInterval 151 | running: sensorData.running 152 | repeat: true 153 | onTriggered: { 154 | compactPlotter.addSample([sensorData.cpuTotalLoad]) 155 | 156 | // Trigger Window::beforeRendering(), which calls Plotter::render() 157 | // Otherwise it will only draw when panel redraws. 158 | // https://github.com/KDE/kdeclarative/blame/master/src/qmlcontrols/kquickcontrolsaddons/plotter.cpp#L766 159 | compactPlotter.Window.window.update() 160 | } 161 | } 162 | } 163 | 164 | Item { 165 | id: pauseIcon 166 | visible: !sensorData.running 167 | 168 | anchors.centerIn: parent 169 | readonly property int minSize: Math.min(parent.width, parent.height) 170 | width: minSize 171 | height: minSize 172 | 173 | Rectangle { 174 | anchors.top: parent.top 175 | anchors.bottom: parent.bottom 176 | anchors.left: parent.left 177 | anchors.margins: parent.width * 1/5 178 | width: parent.width * 1/5 179 | color: "#40FFFFFF" 180 | border.color: "#80000000" 181 | border.width: 1 * units.devicePixelRatio 182 | } 183 | 184 | Rectangle { 185 | anchors.top: parent.top 186 | anchors.bottom: parent.bottom 187 | anchors.right: parent.right 188 | anchors.margins: parent.width * 1/5 189 | width: parent.width * 1/5 190 | color: "#40FFFFFF" 191 | border.color: "#80000000" 192 | border.width: 1 * units.devicePixelRatio 193 | } 194 | } 195 | } 196 | } 197 | 198 | function toggleSensors() { 199 | sensorData.running = !sensorData.running 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /package/translate/merge: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Version: 15 3 | 4 | # https://techbase.kde.org/Development/Tutorials/Localization/i18n_Build_Systems 5 | # Based on: https://github.com/psifidotos/nowdock-plasmoid/blob/master/po/Messages.sh 6 | 7 | DIR=`cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd` 8 | plasmoidName=`kreadconfig5 --file="$DIR/../metadata.desktop" --group="Desktop Entry" --key="X-KDE-PluginInfo-Name"` 9 | widgetName="${plasmoidName##*.}" # Strip namespace 10 | website=`kreadconfig5 --file="$DIR/../metadata.desktop" --group="Desktop Entry" --key="X-KDE-PluginInfo-Website"` 11 | bugAddress="$website" 12 | packageRoot=".." # Root of translatable sources 13 | projectName="plasma_applet_${plasmoidName}" # project name 14 | 15 | #--- 16 | if [ -z "$plasmoidName" ]; then 17 | echo "[merge] Error: Couldn't read plasmoidName." 18 | exit 19 | fi 20 | 21 | if [ -z "$(which xgettext)" ]; then 22 | echo "[merge] Error: xgettext command not found. Need to install gettext" 23 | echo "[merge] Running 'sudo apt install gettext'" 24 | sudo apt install gettext 25 | echo "[merge] gettext installation should be finished. Going back to merging translations." 26 | fi 27 | 28 | #--- 29 | echo "[merge] Extracting messages" 30 | find "${packageRoot}" -name '*.cpp' -o -name '*.h' -o -name '*.c' -o -name '*.qml' -o -name '*.js' | sort > "${DIR}/infiles.list" 31 | 32 | xgettext \ 33 | --from-code=UTF-8 \ 34 | -C -kde -ci18n -ki18n:1 -ki18nc:1c,2 -ki18np:1,2 -ki18ncp:1c,2,3 -ktr2i18n:1 -kI18N_NOOP:1 \ 35 | -kI18N_NOOP2:1c,2 -kN_:1 -kaliasLocale -kki18n:1 -kki18nc:1c,2 -kki18np:1,2 -kki18ncp:1c,2,3 \ 36 | --files-from="infiles.list" \ 37 | --width=200 \ 38 | --add-location=file \ 39 | --package-name="${widgetName}" \ 40 | --package-version="" \ 41 | --msgid-bugs-address="${bugAddress}" \ 42 | -D "${packageRoot}" \ 43 | -D "${DIR}" \ 44 | -o "template.pot.new" \ 45 | || \ 46 | { echo "[merge] error while calling xgettext. aborting."; exit 1; } 47 | 48 | sed -i 's/# SOME DESCRIPTIVE TITLE./'"# Translation of ${widgetName} in LANGUAGE"'/' "template.pot.new" 49 | sed -i 's/# Copyright (C) YEAR THE PACKAGE'"'"'S COPYRIGHT HOLDER/'"# Copyright (C) $(date +%Y)"'/' "template.pot.new" 50 | 51 | if [ -f "template.pot" ]; then 52 | newPotDate=`grep "POT-Creation-Date:" template.pot.new | sed 's/.\{3\}$//'` 53 | oldPotDate=`grep "POT-Creation-Date:" template.pot | sed 's/.\{3\}$//'` 54 | sed -i 's/'"${newPotDate}"'/'"${oldPotDate}"'/' "template.pot.new" 55 | changes=`diff "template.pot" "template.pot.new"` 56 | if [ ! -z "$changes" ]; then 57 | # There's been changes 58 | sed -i 's/'"${oldPotDate}"'/'"${newPotDate}"'/' "template.pot.new" 59 | mv "template.pot.new" "template.pot" 60 | 61 | addedKeys=`echo "$changes" | grep "> msgid" | cut -c 9- | sort` 62 | removedKeys=`echo "$changes" | grep "< msgid" | cut -c 9- | sort` 63 | echo "" 64 | echo "Added Keys:" 65 | echo "$addedKeys" 66 | echo "" 67 | echo "Removed Keys:" 68 | echo "$removedKeys" 69 | echo "" 70 | 71 | else 72 | # No changes 73 | rm "template.pot.new" 74 | fi 75 | else 76 | # template.pot didn't already exist 77 | mv "template.pot.new" "template.pot" 78 | fi 79 | 80 | potMessageCount=`expr $(grep -Pzo 'msgstr ""\n(\n|$)' "template.pot" | grep -c 'msgstr ""')` 81 | echo "| Locale | Lines | % Done|" > "./Status.md" 82 | echo "|----------|---------|-------|" >> "./Status.md" 83 | entryFormat="| %-8s | %7s | %5s |" 84 | templateLine=`perl -e "printf(\"$entryFormat\", \"Template\", \"${potMessageCount}\", \"\")"` 85 | echo "$templateLine" >> "./Status.md" 86 | 87 | rm "${DIR}/infiles.list" 88 | echo "[merge] Done extracting messages" 89 | 90 | #--- 91 | echo "[merge] Merging messages" 92 | catalogs=`find . -name '*.po' | sort` 93 | for cat in $catalogs; do 94 | echo "[merge] $cat" 95 | catLocale=`basename ${cat%.*}` 96 | 97 | widthArg="" 98 | catUsesGenerator=`grep "X-Generator:" "$cat"` 99 | if [ -z "$catUsesGenerator" ]; then 100 | widthArg="--width=400" 101 | fi 102 | 103 | cp "$cat" "$cat.new" 104 | sed -i 's/"Content-Type: text\/plain; charset=CHARSET\\n"/"Content-Type: text\/plain; charset=UTF-8\\n"/' "$cat.new" 105 | 106 | msgmerge \ 107 | ${widthArg} \ 108 | --add-location=file \ 109 | --no-fuzzy-matching \ 110 | -o "$cat.new" \ 111 | "$cat.new" "${DIR}/template.pot" 112 | 113 | sed -i 's/# SOME DESCRIPTIVE TITLE./'"# Translation of ${widgetName} in ${catLocale}"'/' "$cat.new" 114 | sed -i 's/# Translation of '"${widgetName}"' in LANGUAGE/'"# Translation of ${widgetName} in ${catLocale}"'/' "$cat.new" 115 | sed -i 's/# Copyright (C) YEAR THE PACKAGE'"'"'S COPYRIGHT HOLDER/'"# Copyright (C) $(date +%Y)"'/' "$cat.new" 116 | 117 | poEmptyMessageCount=`expr $(grep -Pzo 'msgstr ""\n(\n|$)' "$cat.new" | grep -c 'msgstr ""')` 118 | poMessagesDoneCount=`expr $potMessageCount - $poEmptyMessageCount` 119 | poCompletion=`perl -e "printf(\"%d\", $poMessagesDoneCount * 100 / $potMessageCount)"` 120 | poLine=`perl -e "printf(\"$entryFormat\", \"$catLocale\", \"${poMessagesDoneCount}/${potMessageCount}\", \"${poCompletion}%\")"` 121 | echo "$poLine" >> "./Status.md" 122 | 123 | # mv "$cat" "$cat.old" 124 | mv "$cat.new" "$cat" 125 | done 126 | 127 | # Populate ReadMe.md 128 | sed -i -E 's`share\/plasma\/plasmoids\/(.+)\/translate`share/plasma/plasmoids/'"${plasmoidName}"'/translate`' ./ReadMe.md 129 | if [[ "$website" == *"github.com"* ]]; then 130 | sed -i -E 's`\[new issue\]\(https:\/\/github\.com\/(.+)\/(.+)\/issues\/new\)`[new issue]('"${website}"'/issues/new)`' ./ReadMe.md 131 | fi 132 | sed -i '/^|/ d' ./ReadMe.md # Remove status table from ReadMe 133 | cat ./Status.md >> ./ReadMe.md 134 | rm ./Status.md 135 | 136 | echo "[merge] Done merging messages" 137 | -------------------------------------------------------------------------------- /package/translate/nl_NL.po: -------------------------------------------------------------------------------- 1 | # Translation of sysmonitordash in nl_NL 2 | # Copyright (C) 2018 3 | # This file is distributed under the same license as the sysmonitordash package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: sysmonitordash\n" 9 | "Report-Msgid-Bugs-To: https://github.com/Zren/plasma-applet-sysmonitordash\n" 10 | "POT-Creation-Date: 2019-12-02 17:10-0500\n" 11 | "PO-Revision-Date: 2019-01-25 12:42+0100\n" 12 | "Last-Translator: Heimen Stoffels \n" 13 | "Language-Team: Dutch \n" 14 | "Language: nl_NL\n" 15 | "MIME-Version: 1.0\n" 16 | "Content-Type: text/plain; charset=UTF-8\n" 17 | "Content-Transfer-Encoding: 8bit\n" 18 | "X-Generator: Poedit 2.1.1\n" 19 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 20 | 21 | #: ../contents/config/config.qml 22 | msgid "Sensors" 23 | msgstr "Sensoren" 24 | 25 | #: ../contents/config/config.qml 26 | msgid "Networks" 27 | msgstr "Netwerken" 28 | 29 | #: ../contents/config/config.qml 30 | msgid "Settings" 31 | msgstr "Instellingen" 32 | 33 | #: ../contents/ui/config/ConfigSensors.qml 34 | msgid "Add Sensor" 35 | msgstr "Sensor toevoegen" 36 | 37 | #: ../contents/ui/config/ConfigSensors.qml 38 | msgid "Add Temps" 39 | msgstr "Temperaturen toevoegen" 40 | 41 | #: ../contents/ui/config/ConfigSensors.qml 42 | msgid "Add Fans" 43 | msgstr "Ventilatoren toevoegen" 44 | 45 | #: ../contents/ui/config/ConfigSensors.qml 46 | msgid "Add all lm_sensors" 47 | msgstr "Alle lm_sensors toevoegen" 48 | 49 | #: ../contents/ui/config/ConfigSensors.qml 50 | msgid "Sensor" 51 | msgstr "Sensor" 52 | 53 | #: ../contents/ui/config/ConfigSensors.qml 54 | msgid "Label" 55 | msgstr "Label" 56 | 57 | #: ../contents/ui/config/ConfigSensors.qml 58 | msgid "SubLabel" 59 | msgstr "Sublabel" 60 | 61 | #: ../contents/ui/config/ConfigSensors.qml 62 | msgid "Color" 63 | msgstr "Kleur" 64 | 65 | #: ../contents/ui/config/ConfigSensors.qml 66 | msgid "Icon" 67 | msgstr "Pictogram" 68 | 69 | #: ../contents/ui/config/ConfigSensors.qml 70 | msgid "Units" 71 | msgstr "Eenheden" 72 | 73 | #: ../contents/ui/config/ConfigSensors.qml 74 | msgid "MaxY" 75 | msgstr "MaxY" 76 | 77 | #: ../contents/ui/config/ConfigSettings.qml 78 | msgid "Dashboard Sensors" 79 | msgstr "Dashboardsensors" 80 | 81 | #: ../contents/ui/config/ConfigSettings.qml 82 | msgid "Update Every:" 83 | msgstr "Bijwerken, elke:" 84 | 85 | #: ../contents/ui/config/ConfigSettings.qml 86 | msgid "ms" 87 | msgstr "ms" 88 | 89 | #: ../contents/ui/config/ConfigSettings.qml 90 | msgid "Visible Duration:" 91 | msgstr "Zichtbaar gedurende:" 92 | 93 | #: ../contents/ui/config/ConfigSettings.qml 94 | msgid "sec" 95 | msgstr "sec" 96 | 97 | #: ../contents/ui/config/ConfigSettings.qml 98 | msgid "Panel Icon" 99 | msgstr "Paneelpictogram" 100 | 101 | #: ../contents/ui/DashView.qml 102 | msgid "CPU" 103 | msgstr "CPU" 104 | 105 | #: ../contents/ui/DashView.qml 106 | msgid "Cached" 107 | msgstr "Gecachet" 108 | 109 | #: ../contents/ui/DashView.qml 110 | msgid "Buffered" 111 | msgstr "Gebufferd" 112 | 113 | #: ../contents/ui/DashView.qml 114 | msgid "Apps" 115 | msgstr "Apps" 116 | 117 | #: ../contents/ui/DashView.qml 118 | msgid "Free" 119 | msgstr "Vrij" 120 | 121 | #: ../contents/ui/DashView.qml 122 | msgid "RAM" 123 | msgstr "RAM" 124 | 125 | #: ../contents/ui/DashView.qml 126 | msgid "Used" 127 | msgstr "Gebruikt" 128 | 129 | #: ../contents/ui/DashView.qml 130 | msgid "Swap" 131 | msgstr "Wisselgeheugen" 132 | 133 | #: ../contents/ui/DiskIOGraph.qml 134 | msgid "Read" 135 | msgstr "Uitgelezen" 136 | 137 | #: ../contents/ui/DiskIOGraph.qml 138 | msgid "Write" 139 | msgstr "Weggeschreven" 140 | 141 | #: ../contents/ui/lib/AppletVersion.qml 142 | msgid "Version: %1" 143 | msgstr "Versie: %1" 144 | 145 | #: ../contents/ui/ListBlockDevices.qml 146 | msgctxt "diskName diskLabel" 147 | msgid "%1 (%2)" 148 | msgstr "%1 (%2)" 149 | 150 | #: ../contents/ui/ListBlockDevices.qml 151 | msgid "Disk: %1" 152 | msgstr "Schijf: %1" 153 | 154 | #: ../contents/ui/ListBlockDevices.qml 155 | msgctxt "diskModel sizeGb" 156 | msgid "%1 %2" 157 | msgstr "%1 %2" 158 | 159 | #: ../contents/ui/main.qml 160 | msgid "Start Task Manager" 161 | msgstr "Systeemmonitor starten" 162 | 163 | #: ../contents/ui/NetworkIOGraph.qml 164 | msgid "Download" 165 | msgstr "Gedownload" 166 | 167 | #: ../contents/ui/NetworkIOGraph.qml 168 | msgid "Upload" 169 | msgstr "Geüpload" 170 | 171 | #: ../contents/ui/NetworkIOGraph.qml ../contents/ui/NetworkListDetector.qml 172 | msgid "Network" 173 | msgstr "Netwerk" 174 | 175 | #: ../contents/ui/NetworkListDetector.qml 176 | msgid "Wi-Fi" 177 | msgstr "Wi-Fi" 178 | 179 | #: ../contents/ui/NewUserSplash.qml 180 | msgid "" 181 | "You can hide disks and networks in the config. You can also monitor " 182 | "temperature and fan speed sensors if you have lm-sensors installed." 183 | msgstr "" 184 | "Schijven en netwerken kunnen worden verborgen in de instellingen. Als je lm-" 185 | "sensors installeert, dan kun je ook de temperatuur en ventilatorsnelheid in " 186 | "de gaten houden." 187 | 188 | #: ../contents/ui/NewUserSplash.qml 189 | msgid "Configure Sensors" 190 | msgstr "Sensoren instellen" 191 | 192 | #: ../contents/ui/PartitionUsageBar.qml 193 | msgid "%1
Size: %2
Used: %3
Free: %4" 194 | msgstr "%1
Grootte: %2
Gebruikt: %3
Vrij: %4" 195 | 196 | #: ../contents/ui/SensorGraph.qml 197 | msgctxt "%1 is data value, %2 is unit datatype" 198 | msgid "%1 %2" 199 | msgstr "%1 %2" 200 | 201 | #: ../contents/ui/SensorPresets.qml 202 | msgid "Fan" 203 | msgstr "Ventilator" 204 | 205 | #: ../contents/ui/SensorPresets.qml 206 | msgid "Temp" 207 | msgstr "Temperatuur" 208 | 209 | #: ../contents/ui/SensorPresets.qml 210 | msgid "GPU" 211 | msgstr "GPU" 212 | -------------------------------------------------------------------------------- /package/contents/ui/PlotterCanvas.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import org.kde.plasma.core 2.0 as PlasmaCore 3 | import org.kde.kquickcontrolsaddons 2.0 as KQuickAddons 4 | 5 | 6 | // Based on KQuickAddons.Plotter 7 | // https://github.com/KDE/kdeclarative/blob/master/src/qmlcontrols/kquickcontrolsaddons/plotter.h 8 | // https://github.com/KDE/kdeclarative/blob/master/src/qmlcontrols/kquickcontrolsaddons/plotter.cpp 9 | Canvas { 10 | id: plotter 11 | property real max: 0 12 | property real min: 0 13 | property int sampleSize: 5 14 | property bool stacked: false 15 | property bool autoRange: false 16 | property real rangeMax: 100 17 | property real rangeMin: 0 18 | property color gridColor: '#000' 19 | property int horizontalGridLineCount: 0 20 | property bool normalizeRequested: true 21 | 22 | property var dataSets: [] 23 | 24 | onPaint: { 25 | if (!context) { 26 | getContext("2d") 27 | } 28 | 29 | if (normalizeRequested) { 30 | normalizeData() 31 | normalizeRequested = false 32 | } 33 | 34 | context.clearRect(0, 0, width, height) 35 | 36 | var adjustedMax = autoRange ? max : rangeMax 37 | var adjustedMin = autoRange ? min : rangeMin 38 | var rangeY = adjustedMax - adjustedMin 39 | 40 | var prevPath = [ 41 | [0, height], // bottom left 42 | [width, height], // bottom right 43 | ] 44 | // var dataSetPaths = new Array(dataSets.length) 45 | 46 | for (var i = dataSets.length-1; i >= 0; i--) { 47 | var dataSet = dataSets[i] 48 | // console.log('dataSet', i, 'length=', dataSet.values.length, 'sampleSize=', dataSet.sampleSize, 'max=', adjustedMax, 'min=', adjustedMin) 49 | 50 | //--- Generate curPath 51 | var curPath = new Array(dataSet.normalizedValues.length) 52 | // dataSetPaths[i] = curPath 53 | context.beginPath() 54 | // console.log('dataSet', i, 'normalizedValues.length', dataSet.normalizedValues.length) 55 | for (var j = 0; j < dataSet.normalizedValues.length; j++) { 56 | var value = dataSet.normalizedValues[j] 57 | var x = dataSet.sampleSize >= 2 ? j/(dataSet.sampleSize-1) : 0 58 | var y = (value - adjustedMin) / (rangeY > 0 ? rangeY : 1) 59 | x = x * width 60 | y = y * height 61 | // console.log('\t', j, value, '(', Math.floor(x), Math.floor(y), ')') 62 | y = height - y 63 | curPath[j] = [x, y] 64 | 65 | // Navigate curPath 66 | context.lineTo(x, y) 67 | } 68 | // dataSetPaths[i] = curPath 69 | 70 | // Reverse navigate prevPath 71 | for (var j = prevPath.length-1; j >= 0; j--) { 72 | var p = prevPath[j] 73 | context.lineTo(p[0], p[1]) 74 | } 75 | 76 | // Close and fill 77 | context.closePath() 78 | // context.fillStyle = Qt.rgba(dataSet.color.r, dataSet.color.g, dataSet.color.b, 0.65) 79 | context.fillStyle = dataSet.color 80 | // console.log('dataSet', i, dataSet.color, '=>', context.fillStyle) 81 | context.fill() 82 | 83 | prevPath = curPath 84 | } 85 | 86 | //--- Stroke lines 87 | // context.lineWidth = Math.floor(1 * units.devicePixelRatio) 88 | // for (var i = 0; i < dataSets.length; i++) { 89 | // var dataSet = dataSets[i] 90 | // var curPath = dataSetPaths[i] 91 | // context.beginPath() 92 | // for (var j = 0; j < curPath.length; j++) { 93 | // var p = curPath[j] 94 | // context.lineTo(p[0], p[1]) 95 | // } 96 | // context.strokeStyle = dataSet.color 97 | // console.log('dataSet.stroke', i, dataSet.color) 98 | // context.stroke() 99 | // } 100 | } 101 | 102 | function normalizeData() { 103 | var adjustedMax = Number.NEGATIVE_INFINITY 104 | var adjustedMin = Number.POSITIVE_INFINITY 105 | if (stacked) { 106 | var prevDataSet = null 107 | for (var i = dataSets.length-1; i >= 0; i--) { 108 | var dataSet = dataSets[i] 109 | if (prevDataSet) { 110 | dataSet.normalizedValues = new Array(dataSet.values.length) 111 | for (var j = 0; j < dataSet.values.length; j++) { 112 | var normalizedValue = dataSet.values[j] + prevDataSet.normalizedValues[j] 113 | // if (normalizedValue !== 0) { 114 | // console.log('dataSets[', i, '].normalizedValues[', j, '].normalizedValue', normalizedValue, 'label', sensorGraph.label) 115 | // } 116 | dataSet.normalizedValues[j] = normalizedValue 117 | if (normalizedValue > adjustedMax) { 118 | adjustedMax = normalizedValue 119 | } 120 | if (normalizedValue < adjustedMin) { 121 | adjustedMin = normalizedValue 122 | } 123 | } 124 | } else { 125 | dataSet.normalizedValues = dataSet.values.slice() 126 | if (dataSet.max > adjustedMax) { 127 | adjustedMax = dataSet.max 128 | } 129 | if (dataSet.min > adjustedMin) { 130 | adjustedMin = dataSet.min 131 | } 132 | } 133 | prevDataSet = dataSet 134 | 135 | if (dataSet.max > max) { 136 | max = dataSet.max 137 | } 138 | if (dataSet.min > min) { 139 | min = dataSet.min 140 | } 141 | } 142 | } else { 143 | for (var i = 0; i < dataSets.length; i++) { 144 | var dataSet = dataSets[i] 145 | dataSet.normalizedValues = dataSet.values.slice() 146 | if (dataSet.max > max) { 147 | adjustedMax = max = dataSet.max 148 | } 149 | if (dataSet.min < min) { 150 | adjustedMin = min = dataSet.min 151 | } 152 | } 153 | } 154 | } 155 | 156 | function addSample(values) { 157 | for (var i = 0; i < dataSets.length; i++) { 158 | dataSets[i].addSample(values[i]) 159 | } 160 | var maxValues = new Array(dataSets.length) 161 | var minValues = new Array(dataSets.length) 162 | for (var i = 0; i < dataSets.length; i++) { 163 | maxValues[i] = dataSets[i].max 164 | minValues[i] = dataSets[i].min 165 | } 166 | max = Math.max.apply(null, maxValues) 167 | min = Math.min.apply(null, minValues) 168 | normalizeRequested = true 169 | } 170 | 171 | function updateSampleSize() { 172 | for (var i = 0; i < dataSets.length; i++) { 173 | dataSets[i].setSampleSize(sampleSize) 174 | } 175 | // normalizeRequested = true 176 | } 177 | onSampleSizeChanged: { 178 | updateSampleSize() 179 | } 180 | 181 | onDataSetsChanged: { 182 | updateSampleSize() 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /package/contents/ui/DashView.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.1 2 | import QtQuick.Controls 1.1 3 | import QtQuick.Layouts 1.3 4 | import QtQuick.Window 2.1 5 | 6 | import org.kde.kcoreaddons 1.0 as KCoreAddons 7 | import org.kde.plasma.private.kicker 0.1 as Kicker 8 | 9 | import "lib" 10 | 11 | Kicker.DashboardWindow { 12 | id: window 13 | 14 | backgroundColor: Qt.rgba(0, 0, 0, 0.737) 15 | 16 | onKeyEscapePressed: { 17 | window.close() 18 | } 19 | 20 | mainItem: Item { 21 | anchors.fill: parent 22 | 23 | RowLayout { 24 | anchors.fill: parent 25 | spacing: units.largeSpacing 26 | 27 | ScrollView1 { 28 | id: diskScrollView 29 | Layout.preferredWidth: parent.width / 3 30 | Layout.fillHeight: true 31 | 32 | ColumnLayout { 33 | width: diskScrollView.viewportWidth 34 | spacing: units.largeSpacing * 3 35 | 36 | Repeater { 37 | model: config.diskModel 38 | DiskMonitor { 39 | label: modelData.label 40 | sublabel: modelData.sublabel 41 | partitionId: modelData.partitionId 42 | partitionPaths: modelData.partitionPaths 43 | } 44 | } 45 | } 46 | 47 | } 48 | 49 | ColumnLayout { 50 | Layout.preferredWidth: parent.width / 3 51 | spacing: units.largeSpacing 52 | 53 | SensorGraph { 54 | id: cpuTotalGraph 55 | icon: "cpu" 56 | sensors: { 57 | var l = [] 58 | for (var i = 0; i < sensorData.cpuCount; i++) { 59 | l.push("cpu/cpu" + i + "/TotalLoad") 60 | } 61 | return l 62 | } 63 | defaultMax: sensorData.cpuCount * 100 64 | stacked: true 65 | colors: [ 66 | "#98AF93", 67 | "#708FA3", 68 | "#486F88", 69 | "#29526D", 70 | "#123852", 71 | "#032236", 72 | ] 73 | label: i18n("CPU") 74 | sublabel: plasmoid.configuration.cpuSublabel || deviceData.processorProduct 75 | maxYVisible: false 76 | 77 | function fixedWidth(x, n) { 78 | var s = "" + x 79 | while (s.length < n) { 80 | s = " " + s 81 | } 82 | return s 83 | } 84 | 85 | function formatLabel(value, units) { 86 | return fixedWidth(Math.round(value), 3) + " " + units 87 | } 88 | } 89 | 90 | GridLayout { 91 | id: cpuCoreGrid 92 | Layout.fillWidth: true 93 | 94 | property bool showGrid: sensorData.cpuCount >= 5 95 | visible: showGrid 96 | 97 | property int cellSize: 40 * units.devicePixelRatio 98 | columns: width / cellSize 99 | 100 | Repeater { 101 | model: cpuCoreGrid.showGrid ? sensorData.cpuCount : 0 102 | 103 | SensorGraph { 104 | Layout.preferredHeight: cpuCoreGrid.cellSize 105 | 106 | sensors: ["cpu/cpu" + index + "/TotalLoad"] 107 | defaultMax: 100 108 | colors: ["#708FA3"] 109 | maxYVisible: false 110 | 111 | function formatLabel(value, units) { 112 | return cpuTotalGraph.formatLabel(value, units) 113 | } 114 | 115 | function formatItem(color, label, value, units) { 116 | // We override this function to so that we 117 | // do not draw the cpu color square in the legend. 118 | return formatLabel(value, units) 119 | } 120 | } 121 | 122 | } 123 | } 124 | 125 | SensorGraph { 126 | icon: "media-flash" 127 | sensors: [ 128 | "mem/physical/cached", 129 | "mem/physical/buf", 130 | "mem/physical/application", 131 | ] 132 | legendLabels: [ 133 | i18n("Cached"), 134 | i18n("Buffered"), 135 | i18n("Apps"), 136 | ] 137 | legendItemsBefore: [ 138 | formatItem('transparent', i18n("Free"), sensorData.memFree, ''), 139 | ] 140 | defaultMax: sensorData.memTotal 141 | stacked: true 142 | colors: [ 143 | "#6f936d", 144 | "#aaeeaa", 145 | "#336699", 146 | ] 147 | label: i18n("RAM") 148 | sublabel: plasmoid.configuration.ramSublabel || humanReadableBytes(sensorData.memTotal) 149 | maxYVisible: false 150 | 151 | function formatLabel(value, units) { 152 | return humanReadableBytes(value) + " (" + Math.round(sensorData.memPercentage(value)) + "%)" 153 | } 154 | } 155 | 156 | SensorGraph { 157 | icon: "media-flash" 158 | sensors: [ 159 | "mem/swap/used", 160 | ] 161 | legendLabels: [ 162 | i18n("Used"), 163 | ] 164 | legendItemsBefore: [ 165 | formatItem('transparent', i18n("Free"), sensorData.swapFree, ''), 166 | ] 167 | defaultMax: sensorData.swapTotal 168 | stacked: true 169 | colors: [ 170 | "#6f936d", 171 | ] 172 | label: i18n("Swap") 173 | sublabel: plasmoid.configuration.swapSublabel || humanReadableBytes(sensorData.swapTotal) 174 | maxYVisible: false 175 | 176 | function formatLabel(value, units) { 177 | return humanReadableBytes(value) 178 | } 179 | } 180 | 181 | 182 | ScrollView1 { 183 | id: networkScrollView 184 | Layout.fillWidth: true 185 | Layout.fillHeight: true 186 | 187 | ColumnLayout { 188 | width: networkScrollView.viewportWidth 189 | spacing: units.largeSpacing 190 | 191 | Repeater { 192 | model: config.networkModel 193 | 194 | NetworkIOGraph { 195 | label: modelData.label 196 | icon: modelData.icon 197 | interfaceName: modelData.interfaceName 198 | } 199 | } 200 | 201 | } 202 | } 203 | } 204 | 205 | ScrollView1 { 206 | id: sensorScrollView 207 | Layout.preferredWidth: parent.width / 3 208 | Layout.fillHeight: true 209 | 210 | ColumnLayout { 211 | width: sensorScrollView.viewportWidth 212 | spacing: units.largeSpacing 213 | 214 | NewUserSplash { 215 | id: newUserSplash 216 | } 217 | 218 | Repeater { 219 | model: config.sensorModel 220 | 221 | SensorGraph { 222 | icon: getSensorData('icon', "") 223 | iconOverlays: getSensorData('iconOverlays', []) 224 | sensors: modelData.sensors || [] 225 | colors: getSensorData('colors', []) 226 | defaultMax: getSensorData('defaultMax', 0) 227 | label: getSensorData('label', "") 228 | sublabel: getSensorData('sublabel', "") 229 | valueUnits: modelData.units || sensorUnits 230 | 231 | function getSensorData(key, defaultValue) { 232 | if (typeof modelData === "undefined" || !modelData[key]) { 233 | if (typeof sensorPreset === "undefined" || typeof sensorPreset[key] === "undefined") { 234 | return defaultValue 235 | } else { 236 | return sensorPreset[key] 237 | } 238 | } else { 239 | return modelData[key] 240 | } 241 | } 242 | } 243 | } 244 | 245 | } 246 | } 247 | } // RowLayout 248 | 249 | MouseArea { 250 | anchors.fill: parent 251 | enabled: !newUserSplash.configureButton.hovered 252 | onClicked: window.close() 253 | } 254 | } 255 | 256 | function humanReadableBytes(kibibytes) { 257 | // https://github.com/KDE/kcoreaddons/blob/master/src/lib/util/kformat.h 258 | return KCoreAddons.Format.formatByteSize(kibibytes * 1024) 259 | } 260 | 261 | // function humanReadableBytes(kibibytes) { 262 | // var kilobytes = kibibytes / 1024 * 1000 263 | // if (kilobytes > 1000000000) { 264 | // return i18n("%1 TB", Math.round(kilobytes/1000000000)) 265 | // } else if (kilobytes > 1000000) { 266 | // return i18n("%1 GB", Math.round(kilobytes/1000000)) 267 | // } else if (kilobytes > 1000) { 268 | // return i18n("%1 MB", Math.round(kilobytes/1000)) 269 | // } else { 270 | // return i18n("%1 KB", Math.round(kilobytes)) 271 | // } 272 | // } 273 | } 274 | -------------------------------------------------------------------------------- /package/contents/ui/SensorData.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.2 2 | import org.kde.plasma.core 2.0 as PlasmaCore 3 | 4 | Item { 5 | id: sensorData 6 | 7 | function getData(key) { 8 | if (typeof dataSource.data[key] === 'undefined') return 0 9 | if (typeof dataSource.data[key].value === 'undefined') return 0 10 | return dataSource.data[key].value 11 | } 12 | function getUnits(key) { 13 | if (typeof dataSource.data[key] === 'undefined') return '' 14 | if (typeof dataSource.data[key].units === 'undefined') return '' 15 | return dataSource.data[key].units 16 | } 17 | 18 | readonly property real cpuTotalLoad: getData(dataSource.totalLoad) 19 | readonly property real memApps: getData(dataSource.memApplication) 20 | readonly property real memBuffers: getData(dataSource.memBuffers) 21 | readonly property real memCached: getData(dataSource.memCached) 22 | readonly property real memUsed: getData(dataSource.memUsed) 23 | readonly property real memFree: getData(dataSource.memFree) 24 | readonly property real memTotal: memUsed + memFree 25 | readonly property real swapUsed: getData(dataSource.swapUsed) 26 | readonly property real swapFree: getData(dataSource.swapFree) 27 | readonly property real swapTotal: swapUsed + swapFree 28 | readonly property real cpuTotalLoadRatio: cpuTotalLoad / dataSource.maxCpuLoad 29 | readonly property real cpuTotalLoadPercent: cpuTotalLoadRatio * 100 30 | 31 | // readonly property real memAppsRatio: memTotal ? memApps / memTotal : 0 32 | // readonly property real memAppsPercent: memAppsRatio * 100 33 | // readonly property real memBuffersRatio: memTotal ? memBuffers / memTotal : 0 34 | // readonly property real memBuffersPercent: memBuffersRatio * 100 35 | // readonly property real memCachedRatio: memTotal ? memCached / memTotal : 0 36 | // readonly property real memCachedPercent: memCachedRatio * 100 37 | // readonly property real memUsedRatio: memTotal ? memUsed / memTotal : 0 38 | // readonly property real memUsedPercent: memUsedRatio * 100 39 | // readonly property real memFreeRatio: memTotal ? memFree / memTotal : 0 40 | // readonly property real memFreePercent: memFreeRatio * 100 41 | function memPercentage(value) { 42 | var ratio = memTotal ? value / memTotal : 0 43 | return ratio * 100 44 | } 45 | 46 | readonly property real swapUsedRatio: swapTotal ? swapUsed / swapTotal : 0 47 | readonly property real swapUsedPercent: swapUsedRatio * 100 48 | readonly property real swapFreeRatio: swapTotal ? swapFree / swapTotal : 0 49 | readonly property real swapFreePercent: swapFreeRatio * 100 50 | 51 | readonly property int cpuCount: dataSource.maxCpuIndex + 1 52 | 53 | readonly property var partitionsList: getData(dataSource.partitionsList) 54 | // onPartitionsListChanged: console.log('partitionsList', partitionsList) 55 | 56 | property var networkSensorList: [] 57 | 58 | // /usr/share/plasma/plasmoids/org.kde.plasma.systemloadviewer/contents/ui/SystemLoadViewer.qml 59 | property alias dataSource: dataSource 60 | property int interval: 1000 61 | 62 | property bool running: true 63 | 64 | Timer { 65 | id: timer 66 | repeat: true 67 | running: sensorData.running 68 | interval: dataSource.interval 69 | onTriggered: sensorData.dataTick() 70 | } 71 | signal dataTick() 72 | 73 | PlasmaCore.DataSource { 74 | id: dataSource 75 | engine: "systemmonitor" 76 | 77 | interval: sensorData.running ? sensorData.interval : 2000000000 78 | 79 | 80 | readonly property double maxCpuLoad: 100.0 81 | 82 | property string cpuSystem: "cpu/system/" 83 | property string niceLoad: cpuSystem + "nice" 84 | property string userLoad: cpuSystem + "user" 85 | property string sysLoad: cpuSystem + "sys" 86 | property string ioWait: cpuSystem + "wait" 87 | property string averageClock: cpuSystem + "AverageClock" 88 | property string totalLoad: cpuSystem + "TotalLoad" 89 | property string memPhysical: "mem/physical/" 90 | property string memFree: memPhysical + "free" 91 | property string memApplication: memPhysical + "application" 92 | property string memBuffers: memPhysical + "buf" 93 | property string memCached: memPhysical + "cached" 94 | property string memUsed: memPhysical + "used" 95 | property string swap: "mem/swap/" 96 | property string swapUsed: swap + "used" 97 | property string swapFree: swap + "free" 98 | 99 | property string partitionsList: "partitions/list" 100 | 101 | // property string deviceName: 'enp3s0' 102 | // property string downloadSource: "network/interfaces/" + deviceName + "/receiver/data" 103 | // property string uploadSource: "network/interfaces/" + deviceName + "/transmitter/data" 104 | 105 | property var totalCpuLoadProportions: [.0, .0, .0, .0] 106 | property int maxCpuIndex: 0 107 | property var memoryUsageProportions: [.0, .0, .0] 108 | property double swapUsageProportion: .0 109 | 110 | connectedSources: [niceLoad, userLoad, sysLoad, 111 | ioWait, memFree, memApplication, memBuffers, 112 | memCached, memUsed, swapUsed, swapFree, 113 | averageClock, totalLoad, 114 | partitionsList 115 | ] 116 | 117 | onSourceAdded: { 118 | // console.log('onSourceAdded', source) 119 | var match = source.match(/^cpu\/cpu(\w+)\//) 120 | if (match) { 121 | connectSource(source) 122 | if (maxCpuIndex < match[1]) { 123 | maxCpuIndex = match[1] 124 | } 125 | } 126 | 127 | match = source.match(/^network\/interfaces\/(\w+)\//) 128 | if (match) { 129 | var networkName = match[1] 130 | if (sensorData.networkSensorList.indexOf(networkName) === -1) { 131 | // Add if not seen before 132 | sensorData.networkSensorList.push(networkName) 133 | sensorData.networkSensorListChanged() 134 | } 135 | } 136 | } 137 | onSourceRemoved: { 138 | // console.log('onSourceRemoved', source) 139 | } 140 | 141 | onNewData: { 142 | if (!sensorData.running) { 143 | return // TODO: Disconnect sensors 144 | } 145 | 146 | if (typeof data.value === 'undefined') { 147 | return // skip 148 | } 149 | 150 | // console.log(sourceName, data.value) 151 | 152 | if (sourceName == sysLoad) { 153 | totalCpuLoadProportions[0] = fitCpuLoad(data.value) 154 | } else if (sourceName == userLoad) { 155 | totalCpuLoadProportions[1] = fitCpuLoad(data.value) 156 | } else if (sourceName == niceLoad) { 157 | totalCpuLoadProportions[2] = fitCpuLoad(data.value) 158 | } else if (sourceName == ioWait) { 159 | totalCpuLoadProportions[3] = fitCpuLoad(data.value) 160 | totalCpuLoadProportionsChanged() 161 | } else if (sourceName == memApplication) { 162 | memoryUsageProportions[0] = fitMemoryUsage(data.value) 163 | } else if (sourceName == memBuffers) { 164 | memoryUsageProportions[1] = fitMemoryUsage(data.value) 165 | } else if (sourceName == memCached) { 166 | memoryUsageProportions[2] = fitMemoryUsage(data.value) 167 | memoryUsageProportionsChanged() 168 | } else if (sourceName == swapUsed) { 169 | swapUsageProportion = fitSwapUsage(data.value) 170 | swapUsageProportionChanged() 171 | } 172 | } 173 | 174 | function fitCpuLoad(load) { 175 | var x = load / maxCpuLoad 176 | if (isNaN(x)) { return 0 } 177 | return Math.min(x, 1) // Ensure that we do not get values that might cause problems 178 | } 179 | 180 | function fitMemoryUsage(usage) { 181 | var x = (usage / (parseFloat(dataSource.data[dataSource.memFree].value) + 182 | parseFloat(dataSource.data[dataSource.memUsed].value))) 183 | if (isNaN(x)) { return 0 } 184 | return Math.min(x, 1) 185 | } 186 | 187 | function fitSwapUsage(usage) { 188 | var x = (usage / (parseFloat(usage) + parseFloat(dataSource.data[dataSource.swapFree].value))) 189 | 190 | if (isNaN(x)) { return 0 } 191 | return Math.min(x, 1) 192 | } 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /package/contents/ui/SensorGraph.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.7 2 | import QtQuick.Layouts 1.3 3 | import QtQuick.Window 2.1 4 | import QtQuick.Controls 2.0 // ToolTip 5 | 6 | import org.kde.kquickcontrolsaddons 2.0 as KQuickAddons 7 | // import org.kde.kcoreaddons 1.0 as KCoreAddons 8 | 9 | import "lib" 10 | 11 | Item { 12 | id: sensorGraph 13 | property alias icon: plotter.icon 14 | property alias iconOverlays: plotter.iconOverlays 15 | property alias label: plotter.label 16 | property alias sublabel: plotter.sublabel 17 | property alias maxYLabel: plotter.maxYLabel 18 | property alias maxYVisible: maxYItem.visible 19 | property alias sensors: plotter.sensors 20 | property alias values: plotter.values 21 | property alias maxValue: plotter.maxValue 22 | property alias maxY: plotter.maxY 23 | property alias colors: plotter.colors 24 | property alias stacked: plotter.stacked 25 | property alias defaultMax: plotter.defaultMax 26 | 27 | readonly property var sensorPreset: sensorPresets.getPreset(sensors[0]) 28 | readonly property string sensorUnits: sensorData.getUnits(sensors[0]) 29 | 30 | property string valueUnits: sensorUnits 31 | property var legendLabels: [] 32 | property var legendItemsBefore: [] 33 | 34 | property int padding: 4 * units.devicePixelRatio 35 | property int legendRadius: 6 * units.devicePixelRatio 36 | 37 | property color textColor: "#eeeff0" 38 | 39 | Layout.fillWidth: true 40 | Layout.preferredHeight: 120 * units.devicePixelRatio 41 | 42 | Rectangle { 43 | anchors.fill: parent 44 | color: Qt.rgba(0, 0, 0, 0.2) 45 | border.width: 1 46 | border.color: Qt.rgba(0, 0, 0, 0.8) 47 | } 48 | 49 | // KQuickAddons.Plotter { 50 | PlotterCanvas { 51 | id: plotter 52 | anchors.fill: parent 53 | 54 | property string icon: '' 55 | property alias iconOverlays: iconItem.overlays 56 | property string label: '' 57 | property string sublabel: '' 58 | property string valueSublabel: '' 59 | property string maxYLabel: formatLabel(maxY, sensorGraph.valueUnits) 60 | property var sensors: [] 61 | property var values: [] 62 | 63 | readonly property var maxValue: values.length > 0 ? Math.max.apply(null, values) : 0 64 | property var maxY: 0 65 | onMaxValueChanged: { 66 | var m = 0 67 | for (var j = 0; j < dataSets.length; j++) { 68 | var dataset = dataSets[j] 69 | var datasetMax = dataset.max 70 | if (datasetMax > m) { 71 | m = datasetMax 72 | } 73 | } 74 | maxY = m 75 | } 76 | 77 | property var colors: [theme.highlightColor] 78 | sampleSize: Math.floor(config.visibleDuration / config.sensorInterval) + 1 79 | 80 | 81 | //FIXME: doesn't seem to properly fill otherwise 82 | // Layout.preferredHeight: parent.height 83 | horizontalGridLineCount: 0 84 | 85 | autoRange: defaultMax == 0 86 | rangeMin: 0 87 | property real defaultMax: 0 88 | // rangeMax: { 89 | // if (defaultMax > 0) { 90 | // // console.log(sensor, defaultMax, max, Math.max(defaultMax, max)) 91 | // return Math.max(defaultMax, max) 92 | // } else { 93 | // // console.log(sensor, defaultMax, max) 94 | // return max 95 | // } 96 | // } 97 | rangeMax: defaultMax > 0 ? Math.max(defaultMax, max) : max 98 | 99 | function addZero() { 100 | var values = new Array(plotter.sensors.length) 101 | for (var i = 0; i < plotter.sensors.length; i++) { 102 | values[i] = 0 103 | } 104 | plotter.addSample(values) 105 | } 106 | 107 | Component.onCompleted: { 108 | sensorsChanged() 109 | addZero() 110 | addZero() 111 | } 112 | 113 | Component { 114 | id: plotDataComponent 115 | // KQuickAddons.PlotData {} 116 | PlotDataObj {} 117 | } 118 | onSensorsChanged: { 119 | // console.log(sensor, sensorData.dataSource.connectedSources) 120 | var list = [] 121 | for (var i = 0; i < sensors.length; i++) { 122 | if (!sensors[i]) { 123 | return 124 | } 125 | if (sensorData.dataSource.connectedSources.indexOf(sensors[i]) == -1) { 126 | sensorData.dataSource.connectSource(sensors[i]) 127 | } 128 | 129 | var item = plotDataComponent.createObject(plotter, { 130 | color: plotter.colors[i % plotter.colors.length], 131 | }) 132 | list.push(item) 133 | } 134 | dataSets = list 135 | 136 | // Note: Only need this workaround for KQuickAddons.Plotter, 137 | // PlotterCanvas does not need this workaround. 138 | // Trick Plotter into calling PlotData::setSampleSize() 139 | // var size = plotter.sampleSize 140 | // plotter.sampleSize = plotter.sampleSize + 1 141 | // plotter.sampleSize = size 142 | } 143 | 144 | dataSets: [] 145 | 146 | 147 | AppletIcon { 148 | id: iconItem 149 | visible: plotter.icon 150 | source: plotter.icon 151 | property int size: plotter.sublabel ? labelItem.height * 2 : labelItem.height 152 | width: visible ? size : 0 153 | height: visible ? size : 0 154 | anchors { 155 | left: parent.left 156 | top: parent.top 157 | leftMargin: sensorGraph.padding 158 | topMargin: sensorGraph.padding 159 | } 160 | } 161 | 162 | Item { 163 | id: legendArea 164 | anchors { 165 | left: labelItem.width > sublabelItem.width ? labelItem.right : sublabelItem.right 166 | top: parent.top 167 | bottom: parent.bottom 168 | right: parent.right 169 | 170 | leftMargin: sensorGraph.padding 171 | topMargin: sensorGraph.padding 172 | bottomMargin: sensorGraph.padding 173 | rightMargin: sensorGraph.padding * 8 174 | } 175 | // Rectangle { border.color: "#ff0"; anchors.fill: parent; color: "transparent"; border.width: 1} 176 | 177 | Rectangle { 178 | id: legendBackground 179 | anchors.centerIn: legendGridLayout 180 | width: legendGridLayout.width + sensorGraph.legendRadius*2 181 | height: legendGridLayout.height + sensorGraph.legendRadius*2 182 | color: "#80000000" 183 | radius: sensorGraph.legendRadius 184 | } 185 | 186 | GridLayout { 187 | id: legendGridLayout 188 | rowSpacing: 0 189 | columnSpacing: units.largeSpacing 190 | columns: Math.ceil(itemCount / 4) 191 | readonly property int itemCount: legendRepeaterBefore.count + legendRepeater.count 192 | readonly property int widthMinusSpacing: legendArea.width - columns * columnSpacing 193 | readonly property int maxColumnWidth: widthMinusSpacing / Math.max(1, columns) 194 | 195 | anchors { 196 | // top: parent.top 197 | // topMargin: sensorGraph.padding 198 | 199 | // horizontalCenter: maxYVisible ? parent.horizontalCenter : undefined 200 | // centerIn: parent 201 | verticalCenter: parent.verticalCenter 202 | right: parent.right 203 | } 204 | 205 | Repeater { 206 | id: legendRepeaterBefore 207 | model: legendItemsBefore 208 | TextLabel { 209 | text: modelData 210 | color: sensorGraph.textColor 211 | 212 | // Grow width based on contents, never shrink. 213 | Layout.preferredWidth: 0 214 | onImplicitWidthChanged: { 215 | if (Layout.preferredWidth < implicitWidth) { 216 | Layout.preferredWidth = implicitWidth 217 | } 218 | } 219 | } 220 | } 221 | 222 | Repeater { 223 | id: legendRepeater 224 | model: values.length 225 | TextLabel { 226 | text: { 227 | var label = (index < legendLabels.length) ? legendLabels[index] : '' 228 | return formatItem(colors[index % colors.length], label, values[index], valueUnits) 229 | } 230 | color: sensorGraph.textColor 231 | 232 | // Grow width based on contents, never shrink. 233 | Layout.preferredWidth: 0 234 | onImplicitWidthChanged: { 235 | if (Layout.preferredWidth < implicitWidth) { 236 | Layout.preferredWidth = implicitWidth 237 | } 238 | } 239 | Layout.maximumWidth: legendGridLayout.maxColumnWidth 240 | } 241 | } 242 | 243 | } 244 | 245 | } 246 | 247 | TextLabel { 248 | id: labelItem 249 | anchors { 250 | left: iconItem.right 251 | top: parent.top 252 | topMargin: sensorGraph.padding 253 | } 254 | text: plotter.label || '' 255 | color: sensorGraph.textColor 256 | 257 | // Rectangle { border.color: "#0f0"; anchors.fill: parent; color: "transparent"; border.width: 1} 258 | } 259 | TextLabel { 260 | id: sublabelItem 261 | anchors { 262 | left: iconItem.right 263 | top: labelItem.bottom 264 | rightMargin: sensorGraph.padding 265 | } 266 | text: plotter.sublabel || '' 267 | color: sensorGraph.textColor 268 | opacity: 0.75 269 | 270 | // Rectangle { border.color: "#ff0"; anchors.fill: parent; color: "transparent"; border.width: 1} 271 | } 272 | 273 | TextLabel { 274 | id: maxYItem 275 | anchors { 276 | right: parent.right 277 | top: parent.top 278 | rightMargin: sensorGraph.padding 279 | topMargin: sensorGraph.padding 280 | } 281 | horizontalAlignment: Text.AlignRight 282 | text: plotter.maxYLabel || '' 283 | color: sensorGraph.textColor 284 | opacity: 0.75 285 | } 286 | 287 | Connections { 288 | target: sensorData 289 | onDataTick: { 290 | var values = new Array(plotter.sensors.length) 291 | for (var i = 0; i < plotter.sensors.length; i++) { 292 | values[i] = sensorData.getData(sensors[i]) 293 | } 294 | // console.log('values', values) 295 | plotter.addSample(values) 296 | plotter.values = values 297 | plotter.requestPaint() 298 | } 299 | } 300 | 301 | property int hoveredIndex: -1 302 | property int hoveredIndexX: 0 303 | function updateHoveredIndex() { 304 | if (mouseArea.containsMouse) { 305 | var xOffset = mouseArea.mouseX - mouseArea.x 306 | var xRatio = xOffset / mouseArea.width 307 | 308 | if (plotter.dataSets.length >= 1) { 309 | var datasetLength = plotter.dataSets[0].values.length 310 | hoveredIndex = Math.round(xRatio * (datasetLength-1)) 311 | hoveredIndexX = hoveredIndex / Math.max(1, datasetLength-1) * mouseArea.width 312 | return 313 | } 314 | } 315 | hoveredIndex = -1 316 | hoveredIndexX = 0 317 | } 318 | Connections { 319 | target: mouseArea 320 | onMouseXChanged: plotter.updateHoveredIndex() 321 | onContainsMouseChanged: plotter.updateHoveredIndex() 322 | } 323 | Rectangle { 324 | id: hoverLine 325 | visible: mouseArea.containsMouse 326 | width: 1 * units.devicePixelRatio 327 | height: parent.height 328 | x: plotter.hoveredIndexX 329 | opacity: 0.65 330 | color: "#FFF" 331 | } 332 | 333 | MouseArea { 334 | id: mouseArea 335 | anchors.fill: parent 336 | acceptedButtons: Qt.NoButton 337 | hoverEnabled: true 338 | 339 | ToolTip { 340 | id: tooltip 341 | visible: mouseArea.containsMouse 342 | text: "" 343 | delay: 0 344 | 345 | // minX + maxX workaround the bug where the tooltip appears on the 346 | // other side of the graph when it would hit the edge of the screen. 347 | property int cursorMargin: 3 348 | property int centerCursorX: mouseArea.mouseX - implicitWidth / 2 349 | property int minX: 8 * units.devicePixelRatio 350 | property int maxX: mouseArea.width - implicitWidth - minX 351 | x: Math.max(minX, Math.min(centerCursorX, maxX)) 352 | y: mouseArea.height + cursorMargin 353 | 354 | onVisibleChanged: { 355 | if (visible) { 356 | tooltip.updateText() 357 | } 358 | } 359 | 360 | Connections { 361 | target: plotter 362 | enabled: tooltip.visible 363 | onValuesChanged: { 364 | tooltip.updateText() 365 | } 366 | } 367 | 368 | function updateText() { 369 | text = calcText() 370 | } 371 | 372 | function calcText() { 373 | var xOffset = mouseArea.mouseX - mouseArea.x 374 | var xRatio = xOffset / mouseArea.width 375 | 376 | if (plotter.dataSets.length > 0) { 377 | var datasetLength = plotter.dataSets[0].values.length 378 | var valueIndex = Math.round(xRatio * (datasetLength-1)) 379 | return formatLegend(valueIndex) 380 | } else { 381 | return "" 382 | } 383 | } 384 | } 385 | } 386 | 387 | } 388 | 389 | function formatLabel(value, units) { 390 | // if (units === 'KB') { 391 | // return KCoreAddons.Format.formatByteSize(value * 1024); 392 | // } else { 393 | // return i18nc("%1 is data value, %2 is unit datatype", "%1 %2", Math.round(value), units); 394 | // } 395 | if (units) { 396 | if (units == 'V') { 397 | return i18nc("%1 is data value, %2 is unit datatype", "%1 %2", Number(value).toFixed(2), units) 398 | } else { 399 | return i18nc("%1 is data value, %2 is unit datatype", "%1 %2", Math.round(value), units) 400 | } 401 | } else { 402 | return Math.round(value) 403 | } 404 | } 405 | 406 | function formatValuesLabel() { 407 | var str = '' 408 | for (var i = 0; i < values.length; i++) { 409 | if (i > 0) { 410 | str += "
" 411 | } 412 | var label = (i < legendLabels.length) ? legendLabels[i] : '' 413 | str += formatItem(colors[i % colors.length], label, values[i], valueUnits) 414 | } 415 | return str 416 | } 417 | 418 | 419 | 420 | function stripAlpha(c) { 421 | if (c) { 422 | if (typeof(c) == "string") { 423 | c = Qt.tint(c, "transparent") 424 | } 425 | return Qt.rgba(c.r, c.g, c.b, 1) 426 | } else { 427 | return Qt.rgba(0, 0, 0, 1) 428 | } 429 | } 430 | 431 | function formatLegend(valueIndex) { 432 | var str = "" 433 | for (var j = 0; j < plotter.dataSets.length; j++) { 434 | if (j > 0) { 435 | str += "
" 436 | } 437 | var dataset = plotter.dataSets[j] 438 | var hoveredValue = dataset.values[valueIndex] 439 | var label = '' 440 | 441 | str += formatItem(dataset.color, label, hoveredValue, sensorGraph.valueUnits) 442 | } 443 | 444 | return str 445 | } 446 | 447 | function formatItem(color, label, value, units) { 448 | var str = "" 449 | str += ' ' 450 | if (label) { 451 | str += "" + label + ": " 452 | } 453 | str += formatLabel(value, units) 454 | return str 455 | } 456 | } 457 | --------------------------------------------------------------------------------