[plasma/plasma-systemmonitor] src: Add UI to set CPU affinity
Nate Graham <[email protected]>
| Newsgroups | gmane.comp.kde.cvs |
|---|---|
| Message-ID | <[email protected]> |
Git commit 9fc50b900f37808a6db032b0afb3116ed2ece3c3 by Nate Graham, on behalf of Taras Oleksyn.
Committed on 16/07/2026 at 15:58.
Pushed by ngraham into branch 'master'.
Add UI to set CPU affinity
FEATURE: 429151
FIXED-IN: 6.8.0
M +33 -0 src/faces/applicationstable/contents/ui/FullRepresentation.qml
M +26 -0 src/faces/processtable/contents/ui/FullRepresentation.qml
A +148 -0 src/table/AffinityDialog.qml [License: LGPL(3+eV) LGPL(v3.0) LGPL(v2.1)]
M +1 -0 src/table/CMakeLists.txt
https://invent.kde.org/plasma/plasma-systemmonitor/-/commit/9fc50b900f37808a6db032b0afb3116ed2ece3c3
diff --git a/src/faces/applicationstable/contents/ui/FullRepresentation.qml b/src/faces/applicationstable/contents/ui/FullRepresentation.qml
index 0931b600..2163d2ca 100644
--- a/src/faces/applicationstable/contents/ui/FullRepresentation.qml
+++ b/src/faces/applicationstable/contents/ui/FullRepresentation.qml
@@ -159,6 +159,9 @@ Faces.SensorFace {
} else if (event.key == Qt.Key_F8) {
processHelper.reniceSelection();
event.accepted = true;
+ } else if (Qt.platform.os === "linux" && event.key == Qt.Key_F9) {
+ processHelper.openSetAffinityDialog();
+ event.accepted = true;
}
}
}
@@ -203,6 +206,14 @@ Faces.SensorFace {
enabled: root.quitEnabled
}
+ MenuItem {
+ text: i18nc("@action:inmenu", "Set affinity…")
+ icon.name: "cpu-symbolic"
+ visible: Qt.platform.os === "linux" // only implemented for linux
+ onTriggered: processHelper.openSetAffinityDialog()
+ enabled: root.quitEnabled
+ }
+
Menu {
title: i18nc("@action:inmenu", "Send Signal")
icon.name: "send_signal-symbolic"
@@ -321,6 +332,17 @@ Faces.SensorFace {
}
}
+ Table.AffinityDialog {
+ id: affinityDialog
+
+ onAccepted: {
+ for (let i = 0; i < table.selectedApplications.length; i++) {
+ let pids = Array.from(table.selectedApplications[i].pids)
+ processHelper.setAffinity(pids, affinity)
+ }
+ }
+ }
+
Process.ProcessController {
id: processHelper
@@ -361,6 +383,17 @@ Faces.SensorFace {
reniceDialog.open()
}
+
+ function openSetAffinityDialog() {
+ if (table.selectedApplications.length === 0) {
+ return
+ }
+
+ let pids = table.selectedApplications.reduce((acc, val) => acc.concat(val.pids), [])
+
+ affinityDialog.affinity = processHelper.affinity(pids) ?? []
+ affinityDialog.open()
+ }
}
Table.ColumnConfigurationDialog {
diff --git a/src/faces/processtable/contents/ui/FullRepresentation.qml b/src/faces/processtable/contents/ui/FullRepresentation.qml
index 59a9bcc1..1101359d 100644
--- a/src/faces/processtable/contents/ui/FullRepresentation.qml
+++ b/src/faces/processtable/contents/ui/FullRepresentation.qml
@@ -223,6 +223,9 @@ Faces.SensorFace {
} else if (event.key == Qt.Key_F8) {
processHelper.reniceSelection();
event.accepted = true;
+ } else if (Qt.platform.os === "linux" && event.key == Qt.Key_F9) {
+ processHelper.openSetAffinityDialog();
+ event.accepted = true;
}
}
}
@@ -241,6 +244,13 @@ Faces.SensorFace {
onTriggered: processHelper.reniceSelection()
}
+ MenuItem {
+ text: i18nc("@action:inmenu", "Set affinity…")
+ icon.name: "cpu-symbolic"
+ visible: Qt.platform.os === "linux" // only implemented for linux
+ onTriggered: processHelper.openSetAffinityDialog()
+ }
+
Menu {
title: i18nc("@action:inmenu", "Send Signal")
icon.name: "send_signal-symbolic"
@@ -364,6 +374,15 @@ Faces.SensorFace {
}
}
+ Table.AffinityDialog {
+ id: affinityDialog
+
+ onAccepted: {
+ let pids = table.selectedProcesses.map(i => i.pid)
+ processHelper.setAffinity(pids, affinity)
+ }
+ }
+
Process.ProcessController {
id: processHelper
@@ -394,6 +413,13 @@ Faces.SensorFace {
reniceDialog.open()
}
+
+ function openSetAffinityDialog() {
+ let pids = table.selectedProcesses.map(i => i.pid)
+
+ affinityDialog.affinity = processHelper.affinity(pids)
+ affinityDialog.open()
+ }
}
Table.ColumnConfigurationDialog {
diff --git a/src/table/AffinityDialog.qml b/src/table/AffinityDialog.qml
new file mode 100644
index 00000000..19db4afb
--- /dev/null
+++ b/src/table/AffinityDialog.qml
@@ -0,0 +1,148 @@
+/*
+ * SPDX-FileCopyrightText: 2025 Matthieu Carteron <[email protected]>
+ * SPDX-FileCopyrightText: 2026 Taras Oleksyn <[email protected]>
+ *
+ * SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+ */
+pragma ComponentBehavior: Bound
+
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+
+import org.kde.kirigami as Kirigami
+
+import org.kde.ksysguard.process as Process
+import org.kde.ksysguard.sensors as Sensors
+
+Dialog {
+ id: dialog
+
+ property var affinity: []
+
+ title: i18ndc("plasma-systemmonitor", "@title:window", "Set Affinity")
+ focus: true
+ modal: true
+
+ standardButtons: Dialog.Ok | Dialog.Cancel
+
+ header: Kirigami.DialogHeader {
+ dialog: dialog
+
+ contentItem: Kirigami.DialogHeaderTopContent {
+ dialog: dialog
+ showCloseButton: false
+ }
+ }
+
+ contentItem: RowLayout {
+ spacing: Kirigami.Units.largeSpacing
+
+ Kirigami.Icon {
+ Layout.preferredWidth: affinitySection.height
+ Layout.preferredHeight: affinitySection.height
+ Layout.alignment: Qt.AlignTop
+ source: "cpu"
+ }
+
+ ColumnLayout {
+ id: affinitySection
+ Layout.fillWidth: true
+ spacing: Kirigami.Units.smallSpacing
+
+ Label {
+ text: i18ndc("plasma-systemmonitor", "@label", "Allow to run on:")
+ textFormat: Text.PlainText
+ wrapMode: Text.WordWrap
+ Layout.fillWidth: true
+ }
+
+ GridLayout {
+ id: cpuGrid
+ columns: Math.ceil(Math.sqrt(cpuCountSensor.value))
+ rowSpacing: Kirigami.Units.smallSpacing
+ columnSpacing: Kirigami.Units.largeSpacing
+
+ Repeater {
+ model: cpuCountSensor.value
+
+ CheckBox {
+ id: cpuCheckbox
+ required property int index
+
+ text: i18ndc("plasma-systemmonitor", "@option:check CPU core number", "CPU %1", index + 1)
+
+ Connections {
+ target: dialog
+ function onAffinityChanged() {
+ cpuCheckbox.checked = dialog.affinity ? dialog.affinity.indexOf(cpuCheckbox.index) !== -1 : false
+ }
+ }
+
+ Component.onCompleted: {
+ checked = dialog.affinity ? dialog.affinity.indexOf(index) !== -1 : false
+ }
+
+ onClicked: {
+ let newAffinity = dialog.affinity ? dialog.affinity.slice() : []
+ const currentIndex = newAffinity.indexOf(index)
+
+ if (checked && currentIndex === -1) {
+ newAffinity.push(index)
+ newAffinity.sort((a, b) => a - b)
+ } else if (!checked && currentIndex !== -1) {
+ newAffinity.splice(currentIndex, 1)
+ }
+
+ dialog.affinity = newAffinity
+ }
+ }
+ }
+ }
+
+ RowLayout {
+ Layout.fillWidth: true
+ spacing: Kirigami.Units.smallSpacing
+
+ Button {
+ Layout.fillWidth: true
+ text: i18ndc("plasma-systemmonitor", "@action:button", "Select All")
+ onClicked: {
+ let allCpus = []
+ for (let i = 0; i < cpuCountSensor.value; i++) {
+ allCpus.push(i)
+ }
+ dialog.affinity = allCpus
+ }
+ }
+
+ Button {
+ Layout.fillWidth: true
+ text: i18ndc("plasma-systemmonitor", "@action:button", "Clear All")
+ onClicked: {
+ dialog.affinity = []
+ }
+ }
+
+ Button {
+ Layout.fillWidth: true
+ text: i18ndc("plasma-systemmonitor", "@action:button", "Invert")
+ onClicked: {
+ let inverted = []
+ for (let i = 0; i < cpuCountSensor.value; i++) {
+ if (!dialog.affinity.includes(i)) {
+ inverted.push(i)
+ }
+ }
+ dialog.affinity = inverted
+ }
+ }
+ }
+ }
+ }
+
+ Sensors.Sensor {
+ id: cpuCountSensor
+ sensorId: "cpu/all/coreCount"
+ }
+}
diff --git a/src/table/CMakeLists.txt b/src/table/CMakeLists.txt
index 987e6551..70cfdfa9 100644
--- a/src/table/CMakeLists.txt
+++ b/src/table/CMakeLists.txt
@@ -26,6 +26,7 @@ ecm_target_qml_sources(PlasmaSystemMonitorTable SOURCES
UserCellDelegate.qml
ReniceDialog.qml
KillDialog.qml
+ AffinityDialog.qml
)
target_link_libraries(PlasmaSystemMonitorTable PRIVATE Qt::Quick KSysGuard::Sensors KSysGuard::ProcessCore KF6::CoreAddons KF6::ItemModels)