[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)
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.