├── .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 | 
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 |
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 |
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 |
--------------------------------------------------------------------------------