/* * SPDX-FileCopyrightText: 2026 enne2 * SPDX-License-Identifier: MIT * * CPU Frequency Control — plasmoide KDE Plasma 6 per governor, limiti di * frequenza, EPP, boost e profili per singolo task. * Backend: helper privilegiato /usr/local/libexec/enne2-cpufreq/cpufreq-helper * eseguito via pkexec con azione polkit dedicata (org.enne2.cpufreq.set). */ import QtQuick import QtQuick.Layouts import org.kde.plasma.plasmoid import org.kde.plasma.core as PlasmaCore import org.kde.plasma.components 3.0 as PlasmaComponents import org.kde.plasma.plasma5support as P5Support import org.kde.kirigami as Kirigami PlasmoidItem { id: root // ------------------------------------------------------------------ percorsi readonly property string scriptsDir: Qt.resolvedUrl("../scripts").toString().replace("file://", "") readonly property string stateScript: scriptsDir + "/cpufreq-state.sh" readonly property string taskScript: scriptsDir + "/enne2-cpufreq-task" readonly property string helperPath: "/usr/local/libexec/enne2-cpufreq/cpufreq-helper" readonly property int updateMs: 2000 // ------------------------------------------------------------------- palette readonly property color accentBalanced: "#55c5c0" readonly property color accentQuiet: "#7aa2d6" readonly property color accentSaving: "#8bbf6a" readonly property color accentPerf: "#e0a34e" readonly property color accentCustom: "#b08ccc" readonly property color dim: "#8293a8" readonly property color okColor: "#62c98b" readonly property color warnColor: "#d9a84e" readonly property color critColor: "#e5645f" readonly property var presetModel: [ { key: "quiet", label: "Silenzioso", hint: "boost off · max 35%", color: accentQuiet }, { key: "powersave", label: "Risparmio", hint: "boost off · max 60%", color: accentSaving }, { key: "balanced", label: "Bilanciato", hint: "nessun tetto · boost on", color: accentBalanced }, { key: "performance", label: "Performance", hint: "governor performance", color: accentPerf } ] // --------------------------------------------------------------------- stato property string driver: "" property string cpuMode: "" property string cpuModel: "" property int policies: 0 property string governor: "" property var governors: [] property string epp: "" property var epps: [] property bool eppSupported: false property int boostState: -1 property bool boostSupported: false property int hwMin: 0 property int hwMax: 0 property int floorKhz: 0 property int minKhz: 0 property int maxKhz: 0 property int curKhz: 0 property int curAvgKhz: 0 property real tempC: NaN property real load1: NaN property string profile: "custom" property string profileLabel: "" property string helperState: "unknown" // ok | missing property string polkitState: "unknown" // ok | ask | missing property string conflicts: "" property bool baselinePresent: false property var tasks: [] property bool stateReady: false property bool uiReady: false // ---------------------------------------------------------------- azioni UI property bool busy: false property string message: "" property bool messageIsError: false property bool addFormOpen: false property string newTaskName: "" property string newTaskCommand: "" property int newTaskProfile: 2 property string lastActionSource: "" property int actionCounter: 0 // ------------------------------------------------------------- presentazione Plasmoid.backgroundHints: PlasmaCore.Types.DefaultBackground Plasmoid.icon: "cpu" preferredRepresentation: fullRepresentation toolTipMainText: "CPU Frequency Control" toolTipTextFormat: Text.PlainText toolTipSubText: { if (!root.stateReady) { return "Lettura dello stato in corso…" } var lines = [] lines.push(root.ghz(root.curKhz) + " · governor " + (root.governor || "n/d")) if (root.eppSupported) { lines.push("EPP " + (root.epp || "n/d") + " · boost " + root.boostText()) } lines.push("Profilo: " + (root.profileLabel || root.profile)) lines.push("Limiti " + root.ghzShort(root.minKhz) + " – " + root.ghzShort(root.maxKhz) + " GHz") if (root.tasks.length > 0) { lines.push("Task configurati: " + root.tasks.length) } return lines.join("\n") } readonly property color profileColor: { switch (root.profile) { case "quiet": return accentQuiet case "powersave": return accentSaving case "balanced": return accentBalanced case "performance": return accentPerf default: return accentCustom } } // ---------------------------------------------------------------- formattazione function ghz(khz) { if (!khz || isNaN(khz) || khz <= 0) { return "—" } return (khz / 1000000).toFixed(2) + " GHz" } function ghzShort(khz) { if (!khz || isNaN(khz) || khz <= 0) { return "—" } return (khz / 1000000).toFixed(2) } // Etichette compatte per le pillole EPP (il valore completo resta nel chip in alto) function eppShort(name) { switch (name) { case "balance_performance": return "bal_perf" case "balance_power": return "bal_power" default: return name } } function boostText() { if (!root.boostSupported || root.boostState < 0) { return "n/d" } return root.boostState === 1 ? "on" : "off" } function tempText() { return isNaN(root.tempC) ? "n/d" : Math.round(root.tempC) + " °C" } function tempColor() { if (isNaN(root.tempC)) { return root.dim } if (root.tempC >= 85) { return root.critColor } if (root.tempC >= 70) { return root.warnColor } return root.okColor } // Un solo riquadro di avviso: il problema più grave in ordine di priorità. function setupWarning() { if (root.helperState !== "ok") { return "Backend non installato: esegui ./install.sh --backend (helper in " + root.helperPath + ")" } if (root.polkitState === "missing") { return "Azione polkit assente: senza org.enne2.cpufreq.set le modifiche verranno rifiutate" } if (root.conflicts !== "") { return "Servizio concorrente attivo (" + root.conflicts + ") che gestisce la CPU: può sovrascrivere queste impostazioni" } return "" } function setupWarningColor() { if (root.helperState !== "ok" || root.polkitState === "missing") { return root.critColor } return root.warnColor } // ------------------------------------------------------------- lettura stato P5Support.DataSource { id: stateSource engine: "executable" interval: root.updateMs connectedSources: [root.stateScript] onNewData: function(sourceName, data) { root.applyState(data ? String(data["stdout"] || "") : "") } } // Comandi una tantum: ogni invocazione usa un source univoco (contatore) // perché il motore "executable" non riesegua lo stesso source già noto. P5Support.DataSource { id: actionSource engine: "executable" connectedSources: [] onNewData: function(sourceName, data) { root.finishAction(String(data["stdout"] || ""), String(data["stderr"] || ""), Number(data["exit code"])) actionSource.disconnectSource(sourceName) root.refreshState() } } function applyState(output) { var values = {} var taskList = [] String(output || "").split("\n").forEach(function(rawLine) { if (rawLine.length === 0) { return } if (rawLine.indexOf("task=") === 0) { var parts = rawLine.substring(5).split("\t") if (parts.length >= 3) { try { var task = JSON.parse(parts[2]) task.running = parts[1] === "1" taskList.push(task) } catch (error) { console.warn("enne2-cpufreq: task JSON non valido:", parts[2]) } } return } var separator = rawLine.indexOf("=") if (separator > 0) { values[rawLine.substring(0, separator)] = rawLine.substring(separator + 1).trim() } }) if (values["driver"] !== undefined) { driver = values["driver"] } if (values["mode"] !== undefined) { cpuMode = values["mode"] } if (values["model"] !== undefined) { cpuModel = values["model"] } policies = Number(values["policies"] || 0) governor = values["governor"] || "" governors = (values["governors"] || "").split(" ").filter(function(item, index, array) { return item.length > 0 && array.indexOf(item) === index }) epp = values["epp"] || "" epps = (values["epps"] || "").split(" ").filter(function(item, index, array) { return item.length > 0 && array.indexOf(item) === index }) eppSupported = values["epp_supported"] === "1" boostSupported = values["boost_supported"] === "1" boostState = boostSupported ? Number(values["boost"] || -1) : -1 hwMin = Number(values["hw_min_khz"] || 0) hwMax = Number(values["hw_max_khz"] || 0) floorKhz = Number(values["floor_khz"] || 0) minKhz = Number(values["min_khz"] || 0) maxKhz = Number(values["max_khz"] || 0) curKhz = Number(values["cur_khz"] || 0) curAvgKhz = Number(values["cur_avg_khz"] || 0) var temp = parseFloat(values["temp_mdegc"]) tempC = isNaN(temp) ? NaN : temp / 1000 var load = parseFloat(values["load1"]) load1 = isNaN(load) ? NaN : load profile = values["profile"] || "custom" profileLabel = values["profile_label"] || "" helperState = values["helper"] || "unknown" polkitState = values["polkit"] || "unknown" conflicts = values["conflicts"] || "" baselinePresent = values["baseline"] === "present" tasks = taskList stateReady = values["error"] === undefined if (values["error"] !== undefined && message === "") { message = values["error"] messageIsError = true } syncControls() } function refreshState() { stateSource.disconnectSource(root.stateScript) stateSource.connectSource(root.stateScript) } // ------------------------------------------------------------------ azioni function privileged(args) { return "timeout 90 /usr/bin/pkexec " + root.helperPath + " " + args } function runAction(command) { if (root.busy) { return } root.busy = true if (root.lastActionSource !== "") { actionSource.disconnectSource(root.lastActionSource) } root.actionCounter += 1 // argomento extra ignorato dagli script: rende univoco il source var source = command + " " + root.actionCounter root.lastActionSource = source actionSource.connectSource(source) } function finishAction(output, stderr, exitCode) { root.busy = false var errorText = "" var warningText = "" output.split("\n").forEach(function(line) { if (line.indexOf("error=") === 0) { errorText = line.substring(6) } else if (line.indexOf("warning=") === 0) { warningText = line.substring(8) } }) if (errorText !== "") { root.setMessage(errorText, true) return } if (exitCode !== 0 && exitCode !== undefined && exitCode !== 0) { var detail = stderr.trim() if (detail === "") { detail = "exit code " + exitCode if (exitCode === 124) { detail = "timeout in attesa di polkit" } else if (exitCode === 126) { detail = "autorizzazione negata da polkit" } else if (exitCode === 127) { detail = "helper non trovato: eseguire install.sh" } } root.setMessage(detail, true) return } if (warningText !== "") { root.setMessage(warningText, false) } else if (output.indexOf("ok=1") !== -1) { root.setMessage("Applicato.", false) } } function setMessage(text, isError) { root.message = text root.messageIsError = isError messageTimer.restart() } // Sincronizza i controlli senza rompere i binding durante l'interazione. // uiReady evita ReferenceError quando la rappresentazione piena non è ancora // istanziata (avvio del widget, popup del pannello chiuso): applyState gira // ogni 2 s e senza guardia interrompeva la sincronizzazione dei controlli. function syncControls() { if (!root.uiReady) { return } try { if (!minSlider.pressed) { minSlider.value = root.minKhz } if (!maxSlider.pressed) { maxSlider.value = root.maxKhz } } catch (error) { // la rappresentazione piena non è istanziata in questo momento // (popup chiuso, widget in pannello): ReferenceError atteso root.uiReady = false } } function roundKhz(value) { return Math.max(root.hwMin, Math.min(root.hwMax, Math.round(value / 10000) * 10000)) } // "which" è il cursore rilasciato: serve solo a decidere quale estremo // spostare se l'utente ha trascinato min oltre max (o viceversa). function applyLimits(which) { var lo = roundKhz(minSlider.value) var hi = roundKhz(maxSlider.value) if (lo > hi) { if (which === "min") { hi = lo } else { lo = hi } } runAction(privileged("set-limits " + lo + " " + hi)) } function hexEncode(text) { var input = String(text) var output = "" function byte(value) { return (value < 16 ? "0" : "") + value.toString(16) } for (var i = 0; i < input.length; i++) { var code = input.charCodeAt(i) if (code < 0x80) { output += byte(code) } else if (code < 0x800) { output += byte(0xc0 | (code >> 6)) + byte(0x80 | (code & 0x3f)) } else if (code >= 0xd800 && code <= 0xdbff && i + 1 < input.length) { var low = input.charCodeAt(i + 1) var point = 0x10000 + ((code - 0xd800) << 10) + (low - 0xdc00) i++ output += byte(0xf0 | (point >> 18)) + byte(0x80 | ((point >> 12) & 0x3f)) + byte(0x80 | ((point >> 6) & 0x3f)) + byte(0x80 | (point & 0x3f)) } else { output += byte(0xe0 | (code >> 12)) + byte(0x80 | ((code >> 6) & 0x3f)) + byte(0x80 | (code & 0x3f)) } } return output } function addTask() { var name = root.newTaskName.trim() var command = root.newTaskCommand.trim() if (name === "") { root.setMessage("Il nome del task è obbligatorio.", true) return } if (command === "") { root.setMessage("Il comando del task è obbligatorio.", true) return } var profileKey = root.presetModel[root.newTaskProfile].key runAction(root.taskScript + " add-hex " + hexEncode(name) + " " + hexEncode(profileKey) + " " + hexEncode(command)) root.addFormOpen = false root.newTaskName = "" root.newTaskCommand = "" } Timer { id: messageTimer interval: 6000 repeat: false onTriggered: root.message = "" } // ------------------------------------------------- rappresentazione compatta compactRepresentation: MouseArea { id: compact implicitWidth: compactRow.implicitWidth + Kirigami.Units.smallSpacing * 2 implicitHeight: compactRow.implicitHeight + Kirigami.Units.smallSpacing hoverEnabled: true cursorShape: Qt.PointingHandCursor onClicked: root.expanded = !root.expanded RowLayout { id: compactRow anchors.centerIn: parent spacing: 4 Kirigami.Icon { source: "cpu" implicitWidth: Kirigami.Units.iconSizes.smallMedium implicitHeight: Kirigami.Units.iconSizes.smallMedium color: root.profileColor } PlasmaComponents.Label { text: root.ghzShort(root.curKhz) font.family: "monospace" font.pointSize: 8 color: Kirigami.Theme.textColor } Rectangle { visible: root.boostState === 0 && root.boostSupported implicitWidth: boostLabel.implicitWidth + 6 implicitHeight: boostLabel.implicitHeight + 2 radius: 3 color: Qt.rgba(root.warnColor.r, root.warnColor.g, root.warnColor.b, 0.22) PlasmaComponents.Label { id: boostLabel anchors.centerIn: parent text: "no boost" font.pointSize: 6.5 color: root.warnColor } } } } // ------------------------------------------------------- rappresentazione piena fullRepresentation: Flickable { id: fullItem implicitWidth: Kirigami.Units.gridUnit * 23 implicitHeight: Kirigami.Units.gridUnit * 38 Component.onCompleted: { root.uiReady = true root.syncControls() } Component.onDestruction: root.uiReady = false contentWidth: width contentHeight: content.implicitHeight + Kirigami.Units.smallSpacing * 2 clip: true boundsBehavior: Flickable.StopAtBounds interactive: contentHeight > height flickableDirection: Flickable.VerticalFlick ColumnLayout { id: content x: Kirigami.Units.smallSpacing y: Kirigami.Units.smallSpacing width: fullItem.width - Kirigami.Units.smallSpacing * 2 spacing: 4 // ---------------------------------------------------------- intestazione RowLayout { Layout.fillWidth: true spacing: 6 Kirigami.Icon { source: "cpu" implicitWidth: Kirigami.Units.iconSizes.small implicitHeight: Kirigami.Units.iconSizes.small color: root.profileColor } PlasmaComponents.Label { text: "CPU FREQ" font.family: "monospace" font.bold: true font.pointSize: 8 color: root.profileColor } PlasmaComponents.Label { Layout.fillWidth: true text: root.driver + (root.cpuMode !== "" && root.cpuMode !== "n/a" ? " · " + root.cpuMode : "") font.family: "monospace" font.pointSize: 7.5 color: root.dim elide: Text.ElideRight horizontalAlignment: Text.AlignRight } } // ------------------------------------------------------- frequenza e temperatura RowLayout { Layout.fillWidth: true spacing: Kirigami.Units.smallSpacing ColumnLayout { spacing: 0 PlasmaComponents.Label { text: root.ghz(root.curKhz) font.family: "monospace" font.bold: true font.pointSize: 16 color: Kirigami.Theme.textColor } PlasmaComponents.Label { text: "media " + root.ghz(root.curAvgKhz) + " · " + root.policies + " policy" font.family: "monospace" font.pointSize: 7 color: root.dim } } Item { Layout.fillWidth: true } ColumnLayout { spacing: 0 PlasmaComponents.Label { Layout.alignment: Qt.AlignRight text: root.tempText() font.family: "monospace" font.bold: true font.pointSize: 13 color: root.tempColor() } PlasmaComponents.Label { Layout.alignment: Qt.AlignRight text: isNaN(root.load1) ? "load n/d" : "load " + root.load1.toFixed(2) font.family: "monospace" font.pointSize: 7.5 color: root.dim } } } // Barra: intervallo hardware, finestra consentita (min–max), cursore corrente Item { id: freqBar Layout.fillWidth: true Layout.preferredHeight: 16 readonly property real rangeMin: root.hwMin > 0 ? root.hwMin : 1 readonly property real rangeMax: root.hwMax > rangeMin ? root.hwMax : rangeMin + 1 function positionOf(khz) { var ratio = (khz - rangeMin) / (rangeMax - rangeMin) return Math.max(0, Math.min(1, ratio)) } Rectangle { anchors.fill: parent radius: 3 color: Qt.rgba(1, 1, 1, 0.07) } Rectangle { id: allowedWindow anchors.verticalCenter: parent.verticalCenter height: parent.height - 6 radius: 2 x: freqBar.width * freqBar.positionOf(root.minKhz) width: Math.max(2, freqBar.width * (freqBar.positionOf(root.maxKhz) - freqBar.positionOf(root.minKhz))) color: Qt.rgba(root.profileColor.r, root.profileColor.g, root.profileColor.b, 0.30) border.width: 1 border.color: Qt.rgba(root.profileColor.r, root.profileColor.g, root.profileColor.b, 0.55) } Rectangle { width: 3 height: parent.height radius: 1 color: Kirigami.Theme.textColor x: Math.min(freqBar.width - width, Math.max(0, freqBar.width * freqBar.positionOf(root.curKhz) - width / 2)) } } // ------------------------------------------------------------ chip di stato Flow { Layout.fillWidth: true Layout.preferredHeight: childrenRect.height spacing: 4 Rectangle { implicitWidth: govLabel.implicitWidth + 10 implicitHeight: govLabel.implicitHeight + 4 radius: 4 color: Qt.rgba(0, 0, 0, 0.28) border.width: 1 border.color: Qt.rgba(1, 1, 1, 0.10) PlasmaComponents.Label { id: govLabel anchors.centerIn: parent text: "governor " + (root.governor || "n/d") font.family: "monospace" font.pointSize: 7.5 color: Kirigami.Theme.textColor opacity: 0.8 } } Rectangle { visible: root.eppSupported implicitWidth: eppLabel.implicitWidth + 10 implicitHeight: eppLabel.implicitHeight + 4 radius: 4 color: Qt.rgba(0, 0, 0, 0.28) border.width: 1 border.color: Qt.rgba(1, 1, 1, 0.10) PlasmaComponents.Label { id: eppLabel anchors.centerIn: parent text: "epp " + (root.epp || "n/d") font.family: "monospace" font.pointSize: 7.5 color: Kirigami.Theme.textColor opacity: 0.8 } } Rectangle { visible: root.boostSupported implicitWidth: boostChip.implicitWidth + 10 implicitHeight: boostChip.implicitHeight + 4 radius: 4 color: root.boostState === 1 ? Qt.rgba(root.okColor.r, root.okColor.g, root.okColor.b, 0.14) : Qt.rgba(root.warnColor.r, root.warnColor.g, root.warnColor.b, 0.16) border.width: 1 border.color: root.boostState === 1 ? Qt.rgba(root.okColor.r, root.okColor.g, root.okColor.b, 0.4) : Qt.rgba(root.warnColor.r, root.warnColor.g, root.warnColor.b, 0.4) PlasmaComponents.Label { id: boostChip anchors.centerIn: parent text: "boost " + root.boostText() font.family: "monospace" font.pointSize: 7.5 color: root.boostState === 1 ? root.okColor : root.warnColor } } Rectangle { visible: root.baselinePresent implicitWidth: baseLabel.implicitWidth + 10 implicitHeight: baseLabel.implicitHeight + 4 radius: 4 color: Qt.rgba(root.accentPerf.r, root.accentPerf.g, root.accentPerf.b, 0.14) border.width: 1 border.color: Qt.rgba(root.accentPerf.r, root.accentPerf.g, root.accentPerf.b, 0.4) PlasmaComponents.Label { id: baseLabel anchors.centerIn: parent text: "baseline salvata" font.family: "monospace" font.pointSize: 7.5 color: root.accentPerf } } } // ----------------------------------------------- avvisi di configurazione Rectangle { Layout.fillWidth: true Layout.preferredHeight: setupLabel.implicitHeight + 6 visible: root.setupWarning() !== "" radius: 4 color: Qt.rgba(root.setupWarningColor().r, root.setupWarningColor().g, root.setupWarningColor().b, 0.14) border.width: 1 border.color: Qt.rgba(root.setupWarningColor().r, root.setupWarningColor().g, root.setupWarningColor().b, 0.45) PlasmaComponents.Label { id: setupLabel anchors.centerIn: parent width: parent.width - 10 wrapMode: Text.WordWrap maximumLineCount: 2 elide: Text.ElideRight horizontalAlignment: Text.AlignHCenter font.pointSize: 7 color: root.setupWarningColor() text: root.setupWarning() } } // --------------------------------------------------------------- profili PlasmaComponents.Label { text: "PROFILO" font.family: "monospace" font.pointSize: 7.5 font.bold: true color: root.dim } Grid { Layout.fillWidth: true columns: 2 spacing: 4 Repeater { model: root.presetModel delegate: Rectangle { id: presetButton required property var modelData readonly property bool active: root.profile === modelData.key width: (parent.width - 4) / 2 height: 26 radius: 5 color: active ? Qt.rgba(modelData.color.r, modelData.color.g, modelData.color.b, 0.22) : (presetHover.containsMouse ? Qt.rgba(1, 1, 1, 0.10) : Qt.rgba(1, 1, 1, 0.05)) border.width: 1 border.color: active ? Qt.rgba(modelData.color.r, modelData.color.g, modelData.color.b, 0.8) : Qt.rgba(1, 1, 1, 0.10) opacity: root.busy ? 0.6 : 1.0 Behavior on color { ColorAnimation { duration: 120 } } ColumnLayout { anchors.centerIn: parent spacing: -2 PlasmaComponents.Label { Layout.alignment: Qt.AlignHCenter text: presetButton.modelData.label font.family: "monospace" font.pointSize: 8.5 font.bold: presetButton.active color: presetButton.active ? presetButton.modelData.color : Kirigami.Theme.textColor } PlasmaComponents.Label { Layout.alignment: Qt.AlignHCenter text: presetButton.modelData.hint font.family: "monospace" font.pointSize: 6.5 color: root.dim } } MouseArea { id: presetHover anchors.fill: parent hoverEnabled: true cursorShape: Qt.PointingHandCursor onClicked: root.runAction(root.privileged("apply " + presetButton.modelData.key)) } } } } // -------------------------------------------------------------- governor PlasmaComponents.Label { text: "GOVERNOR" font.family: "monospace" font.pointSize: 7.5 font.bold: true color: root.dim } Flow { Layout.fillWidth: true Layout.preferredHeight: childrenRect.height spacing: 4 Repeater { model: root.governors delegate: ChoicePill { required property string modelData text: modelData active: root.governor === modelData accent: root.profileColor enabled: !root.busy onClicked: root.runAction(root.privileged("set-governor " + modelData)) } } } // ------------------------------------------------------------------- EPP PlasmaComponents.Label { text: "EPP" font.family: "monospace" font.pointSize: 7 font.bold: true color: root.dim } Flow { Layout.fillWidth: true Layout.preferredHeight: childrenRect.height visible: root.eppSupported spacing: 4 Repeater { model: root.epps delegate: ChoicePill { required property string modelData text: root.eppShort(modelData) active: root.epp === modelData accent: root.profileColor enabled: !root.busy onClicked: root.runAction(root.privileged("set-epp " + modelData)) } } } // ---------------------------------------------------------------- limiti RowLayout { Layout.fillWidth: true spacing: 6 PlasmaComponents.Label { text: "LIMITI" font.family: "monospace" font.pointSize: 7.5 font.bold: true color: root.dim } Item { Layout.fillWidth: true } PlasmaComponents.Label { text: root.ghz(root.minKhz) + " – " + root.ghz(root.maxKhz) font.family: "monospace" font.pointSize: 7.5 color: root.profileColor } PlasmaComponents.Label { text: "hw " + root.ghzShort(root.hwMin) + "–" + root.ghzShort(root.hwMax) font.family: "monospace" font.pointSize: 7 color: root.dim } } RowLayout { Layout.fillWidth: true spacing: 6 PlasmaComponents.Label { text: "min" font.family: "monospace" font.pointSize: 7.5 color: root.dim Layout.preferredWidth: 26 } PlasmaComponents.Slider { id: minSlider Layout.fillWidth: true from: root.hwMin to: root.hwMax enabled: root.hwMax > root.hwMin && !root.busy onPressedChanged: if (!pressed) root.applyLimits("min") } PlasmaComponents.Label { text: root.ghzShort(minSlider.pressed ? minSlider.value : root.minKhz) font.family: "monospace" font.pointSize: 7.5 color: Kirigami.Theme.textColor horizontalAlignment: Text.AlignRight Layout.preferredWidth: 32 } } RowLayout { Layout.fillWidth: true spacing: 6 PlasmaComponents.Label { text: "max" font.family: "monospace" font.pointSize: 7.5 color: root.dim Layout.preferredWidth: 26 } PlasmaComponents.Slider { id: maxSlider Layout.fillWidth: true from: root.hwMin to: root.hwMax enabled: root.hwMax > root.hwMin && !root.busy onPressedChanged: if (!pressed) root.applyLimits("max") } PlasmaComponents.Label { text: root.ghzShort(maxSlider.pressed ? maxSlider.value : root.maxKhz) font.family: "monospace" font.pointSize: 7.5 color: Kirigami.Theme.textColor horizontalAlignment: Text.AlignRight Layout.preferredWidth: 32 } } // ----------------------------------------------------------------- boost RowLayout { Layout.fillWidth: true spacing: 4 PlasmaComponents.Label { Layout.fillWidth: true text: "BOOST / TURBO" font.family: "monospace" font.pointSize: 7.5 font.bold: true color: root.dim } ChoicePill { text: "on" active: root.boostState === 1 accent: root.okColor enabled: root.boostSupported && !root.busy && root.boostState !== 1 onClicked: root.runAction(root.privileged("set-boost 1")) } ChoicePill { text: "off" active: root.boostState === 0 accent: root.warnColor enabled: root.boostSupported && !root.busy && root.boostState !== 0 onClicked: root.runAction(root.privileged("set-boost 0")) } } // ------------------------------------------------------------------ task RowLayout { Layout.fillWidth: true spacing: 6 PlasmaComponents.Label { text: "TASK CON PROFILO DEDICATO" font.family: "monospace" font.pointSize: 7.5 font.bold: true color: root.dim } Item { Layout.fillWidth: true } PlasmaComponents.ToolButton { icon.name: root.addFormOpen ? "dialog-cancel" : "list-add" text: root.addFormOpen ? "Annulla" : "Nuovo" display: PlasmaComponents.ToolButton.TextBesideIcon onClicked: root.addFormOpen = !root.addFormOpen } } Repeater { model: root.tasks delegate: Rectangle { id: taskRow required property var modelData Layout.fillWidth: true implicitHeight: 26 radius: 5 color: modelData.running ? Qt.rgba(root.accentPerf.r, root.accentPerf.g, root.accentPerf.b, 0.16) : Qt.rgba(1, 1, 1, 0.05) RowLayout { anchors.fill: parent anchors.leftMargin: 6 anchors.rightMargin: 4 spacing: 4 Rectangle { Layout.alignment: Qt.AlignVCenter width: 6 height: 6 radius: 3 color: taskRow.modelData.running ? root.okColor : root.dim opacity: taskRow.modelData.running ? 1 : 0.5 } PlasmaComponents.Label { Layout.fillWidth: true text: taskRow.modelData.name elide: Text.ElideRight font.pointSize: 8 color: Kirigami.Theme.textColor } PlasmaComponents.Label { text: taskRow.modelData.profile font.family: "monospace" font.pointSize: 7 color: root.dim } PlasmaComponents.ToolButton { implicitWidth: 22 implicitHeight: 22 icon.name: taskRow.modelData.running ? "media-playback-stop" : "media-playback-start" enabled: !root.busy onClicked: { var verb = taskRow.modelData.running ? " stop " : " run " root.runAction(root.taskScript + verb + taskRow.modelData.slug) } } PlasmaComponents.ToolButton { implicitWidth: 22 implicitHeight: 22 icon.name: "edit-delete" enabled: !root.busy && !taskRow.modelData.running onClicked: root.runAction(root.taskScript + " remove " + taskRow.modelData.slug) } } } } // ------------------------------------------------------- form nuovo task ColumnLayout { Layout.fillWidth: true spacing: 4 visible: root.addFormOpen PlasmaComponents.TextField { Layout.fillWidth: true placeholderText: "Nome del task (es. Render Blender)" text: root.newTaskName onTextChanged: root.newTaskName = text } PlasmaComponents.TextField { Layout.fillWidth: true placeholderText: "Comando (es. blender -b scena.blend -f 1)" text: root.newTaskCommand onTextChanged: root.newTaskCommand = text } PlasmaComponents.ComboBox { Layout.fillWidth: true model: root.presetModel textRole: "label" currentIndex: root.newTaskProfile onActivated: root.newTaskProfile = currentIndex } RowLayout { Layout.fillWidth: true spacing: 4 Item { Layout.fillWidth: true } PlasmaComponents.Button { text: "Aggiungi" icon.name: "list-add" enabled: root.newTaskName.trim() !== "" && root.newTaskCommand.trim() !== "" onClicked: root.addTask() } } } // ---------------------------------------------------------------- footer RowLayout { Layout.fillWidth: true spacing: 4 PlasmaComponents.Label { Layout.fillWidth: true text: root.message !== "" ? root.message : (root.busy ? "Applico…" : "Pronto") color: root.messageIsError ? root.critColor : root.dim font.pointSize: 7.5 elide: Text.ElideRight } PlasmaComponents.ToolButton { icon.name: "view-refresh" text: "Reset" display: PlasmaComponents.ToolButton.TextBesideIcon enabled: !root.busy onClicked: root.runAction(root.privileged("reset")) } } } } }