1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-01-24 13:32:50 -05:00
Files
DankMaterialShell/quickshell/Modules/ProcessList/ProcessListPopout.qml
2026-01-18 21:03:34 -05:00

502 lines
19 KiB
QML

import QtQuick
import QtQuick.Layouts
import qs.Common
import qs.Modules.ProcessList
import qs.Services
import qs.Widgets
DankPopout {
id: processListPopout
layerNamespace: "dms:process-list-popout"
property var parentWidget: null
property var triggerScreen: null
property string searchText: ""
property string expandedPid: ""
function hide() {
close();
if (processContextMenu.visible)
processContextMenu.close();
}
function show() {
open();
}
popupWidth: 650
popupHeight: 550
triggerWidth: 55
positioning: ""
screen: triggerScreen
shouldBeVisible: false
onBackgroundClicked: {
if (processContextMenu.visible)
processContextMenu.close();
close();
}
onShouldBeVisibleChanged: {
if (!shouldBeVisible) {
searchText = "";
expandedPid = "";
if (processesView)
processesView.reset();
}
}
Ref {
service: DgopService
}
ProcessContextMenu {
id: processContextMenu
}
content: Component {
Rectangle {
id: processListContent
LayoutMirroring.enabled: I18n.isRtl
LayoutMirroring.childrenInherit: true
radius: Theme.cornerRadius
color: "transparent"
clip: true
focus: true
Component.onCompleted: {
if (processListPopout.shouldBeVisible)
forceActiveFocus();
processContextMenu.parent = processListContent;
processContextMenu.parentFocusItem = processListContent;
}
Keys.onPressed: event => {
if (processContextMenu.visible)
return;
switch (event.key) {
case Qt.Key_Escape:
if (processListPopout.searchText.length > 0) {
processListPopout.searchText = "";
event.accepted = true;
return;
}
if (processesView.keyboardNavigationActive) {
processesView.reset();
event.accepted = true;
return;
}
processListPopout.close();
event.accepted = true;
return;
case Qt.Key_F:
if (event.modifiers & Qt.ControlModifier) {
searchField.forceActiveFocus();
event.accepted = true;
return;
}
break;
}
processesView.handleKey(event);
}
Connections {
target: processListPopout
function onShouldBeVisibleChanged() {
if (processListPopout.shouldBeVisible)
Qt.callLater(() => processListContent.forceActiveFocus());
}
}
ColumnLayout {
anchors.fill: parent
anchors.margins: Theme.spacingS
spacing: Theme.spacingS
RowLayout {
Layout.fillWidth: true
spacing: Theme.spacingM
Row {
spacing: Theme.spacingS
DankIcon {
name: "analytics"
size: Theme.iconSize
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: I18n.tr("Processes")
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Bold
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
}
Item {
Layout.fillWidth: true
}
DankTextField {
id: searchField
Layout.preferredWidth: Theme.fontSizeMedium * 14
Layout.preferredHeight: Theme.fontSizeMedium * 2.5
placeholderText: I18n.tr("Search...")
leftIconName: "search"
showClearButton: true
text: processListPopout.searchText
onTextChanged: processListPopout.searchText = text
ignoreUpDownKeys: true
keyForwardTargets: [processListContent]
}
}
Item {
id: statsContainer
Layout.fillWidth: true
Layout.preferredHeight: Math.max(leftInfo.height, gaugesRow.height) + Theme.spacingS
function compactMem(kb) {
if (kb < 1024 * 1024) {
const mb = kb / 1024;
return mb >= 100 ? mb.toFixed(0) + " MB" : mb.toFixed(1) + " MB";
}
const gb = kb / (1024 * 1024);
return gb >= 10 ? gb.toFixed(0) + " GB" : gb.toFixed(1) + " GB";
}
readonly property real gaugeSize: Theme.fontSizeMedium * 6.5
readonly property var enabledGpusWithTemp: {
if (!SessionData.enabledGpuPciIds || SessionData.enabledGpuPciIds.length === 0)
return [];
const result = [];
for (const gpu of DgopService.availableGpus) {
if (SessionData.enabledGpuPciIds.indexOf(gpu.pciId) !== -1 && gpu.temperature > 0)
result.push(gpu);
}
return result;
}
readonly property bool hasGpu: enabledGpusWithTemp.length > 0
Row {
id: leftInfo
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingM
Rectangle {
width: Theme.fontSizeMedium * 3
height: width
radius: Theme.cornerRadius
color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.1)
SystemLogo {
anchors.centerIn: parent
width: parent.width * 0.7
height: width
colorOverride: Theme.primary
}
}
Column {
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingXS / 2
StyledText {
text: DgopService.hostname || "localhost"
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Bold
color: Theme.surfaceText
}
StyledText {
text: DgopService.distribution || "Linux"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
Row {
spacing: Theme.spacingS
Row {
spacing: Theme.spacingXS
DankIcon {
name: "schedule"
size: Theme.fontSizeSmall
color: Theme.surfaceVariantText
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: DgopService.shortUptime || "--"
font.pixelSize: Theme.fontSizeSmall - 1
font.family: SettingsData.monoFontFamily
color: Theme.surfaceVariantText
}
}
StyledText {
text: "•"
font.pixelSize: Theme.fontSizeSmall - 1
color: Theme.surfaceVariantText
}
StyledText {
text: DgopService.processCount + " " + I18n.tr("procs", "short for processes")
font.pixelSize: Theme.fontSizeSmall - 1
font.family: SettingsData.monoFontFamily
color: Theme.surfaceVariantText
}
}
}
}
Row {
id: gaugesRow
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingS
CircleGauge {
width: statsContainer.gaugeSize
height: statsContainer.gaugeSize
value: DgopService.cpuUsage / 100
label: DgopService.cpuUsage.toFixed(0) + "%"
sublabel: "CPU"
detail: DgopService.cpuTemperature > 0 ? (DgopService.cpuTemperature.toFixed(0) + "°") : ""
accentColor: DgopService.cpuUsage > 80 ? Theme.error : (DgopService.cpuUsage > 50 ? Theme.warning : Theme.primary)
detailColor: DgopService.cpuTemperature > 85 ? Theme.error : (DgopService.cpuTemperature > 70 ? Theme.warning : Theme.surfaceVariantText)
}
CircleGauge {
width: statsContainer.gaugeSize
height: statsContainer.gaugeSize
value: DgopService.memoryUsage / 100
label: statsContainer.compactMem(DgopService.usedMemoryKB)
sublabel: I18n.tr("Memory")
detail: DgopService.totalSwapKB > 0 ? ("+" + statsContainer.compactMem(DgopService.usedSwapKB)) : ""
accentColor: DgopService.memoryUsage > 90 ? Theme.error : (DgopService.memoryUsage > 70 ? Theme.warning : Theme.secondary)
}
CircleGauge {
width: statsContainer.gaugeSize
height: statsContainer.gaugeSize
visible: statsContainer.hasGpu
readonly property var gpu: statsContainer.enabledGpusWithTemp[0] ?? null
readonly property color vendorColor: {
const vendor = (gpu?.vendor ?? "").toLowerCase();
if (vendor.includes("nvidia"))
return Theme.success;
if (vendor.includes("amd"))
return Theme.error;
if (vendor.includes("intel"))
return Theme.info;
return Theme.info;
}
value: Math.min(1, (gpu?.temperature ?? 0) / 100)
label: (gpu?.temperature ?? 0) > 0 ? ((gpu?.temperature ?? 0).toFixed(0) + "°C") : "--"
sublabel: "GPU"
accentColor: {
const temp = gpu?.temperature ?? 0;
if (temp > 85)
return Theme.error;
if (temp > 70)
return Theme.warning;
return vendorColor;
}
}
}
}
Rectangle {
Layout.fillWidth: true
Layout.fillHeight: true
radius: Theme.cornerRadius
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
clip: true
ProcessesView {
id: processesView
anchors.fill: parent
anchors.margins: Theme.spacingS
searchText: processListPopout.searchText
expandedPid: processListPopout.expandedPid
contextMenu: processContextMenu
onExpandedPidChanged: processListPopout.expandedPid = expandedPid
}
}
}
}
}
component CircleGauge: Item {
id: gaugeRoot
property real value: 0
property string label: ""
property string sublabel: ""
property string detail: ""
property color accentColor: Theme.primary
property color detailColor: Theme.surfaceVariantText
readonly property real thickness: Math.max(4, Math.min(width, height) / 15)
readonly property real glowExtra: thickness * 1.4
readonly property real arcPadding: thickness / 1.3
readonly property real innerDiameter: width - (arcPadding + thickness + glowExtra) * 2
readonly property real maxTextWidth: innerDiameter * 0.9
readonly property real baseLabelSize: Math.round(width * 0.18)
readonly property real labelSize: Math.round(Math.min(baseLabelSize, maxTextWidth / Math.max(1, label.length * 0.65)))
readonly property real sublabelSize: Math.round(Math.min(width * 0.13, maxTextWidth / Math.max(1, sublabel.length * 0.7)))
readonly property real detailSize: Math.round(Math.min(width * 0.12, maxTextWidth / Math.max(1, detail.length * 0.65)))
property real animValue: 0
onValueChanged: animValue = Math.min(1, Math.max(0, value))
Behavior on animValue {
NumberAnimation {
duration: Theme.mediumDuration
easing.type: Easing.OutCubic
}
}
Component.onCompleted: animValue = Math.min(1, Math.max(0, value))
Canvas {
id: glowCanvas
anchors.fill: parent
onPaint: {
const ctx = getContext("2d");
ctx.reset();
const cx = width / 2;
const cy = height / 2;
const radius = (Math.min(width, height) / 2) - gaugeRoot.arcPadding;
const startAngle = -Math.PI * 0.5;
const endAngle = Math.PI * 1.5;
ctx.lineCap = "round";
if (gaugeRoot.animValue > 0) {
const prog = startAngle + (endAngle - startAngle) * gaugeRoot.animValue;
ctx.beginPath();
ctx.arc(cx, cy, radius, startAngle, prog);
ctx.strokeStyle = Qt.rgba(gaugeRoot.accentColor.r, gaugeRoot.accentColor.g, gaugeRoot.accentColor.b, 0.2);
ctx.lineWidth = gaugeRoot.thickness + gaugeRoot.glowExtra;
ctx.stroke();
}
}
Connections {
target: gaugeRoot
function onAnimValueChanged() {
glowCanvas.requestPaint();
}
function onAccentColorChanged() {
glowCanvas.requestPaint();
}
function onWidthChanged() {
glowCanvas.requestPaint();
}
function onHeightChanged() {
glowCanvas.requestPaint();
}
}
Component.onCompleted: requestPaint()
}
Canvas {
id: arcCanvas
anchors.fill: parent
onPaint: {
const ctx = getContext("2d");
ctx.reset();
const cx = width / 2;
const cy = height / 2;
const radius = (Math.min(width, height) / 2) - gaugeRoot.arcPadding;
const startAngle = -Math.PI * 0.5;
const endAngle = Math.PI * 1.5;
ctx.lineCap = "round";
ctx.beginPath();
ctx.arc(cx, cy, radius, startAngle, endAngle);
ctx.strokeStyle = Qt.rgba(gaugeRoot.accentColor.r, gaugeRoot.accentColor.g, gaugeRoot.accentColor.b, 0.1);
ctx.lineWidth = gaugeRoot.thickness;
ctx.stroke();
if (gaugeRoot.animValue > 0) {
const prog = startAngle + (endAngle - startAngle) * gaugeRoot.animValue;
ctx.beginPath();
ctx.arc(cx, cy, radius, startAngle, prog);
ctx.strokeStyle = gaugeRoot.accentColor;
ctx.lineWidth = gaugeRoot.thickness;
ctx.stroke();
}
}
Connections {
target: gaugeRoot
function onAnimValueChanged() {
arcCanvas.requestPaint();
}
function onAccentColorChanged() {
arcCanvas.requestPaint();
}
function onWidthChanged() {
arcCanvas.requestPaint();
}
function onHeightChanged() {
arcCanvas.requestPaint();
}
}
Component.onCompleted: requestPaint()
}
Column {
anchors.centerIn: parent
spacing: 1
StyledText {
text: gaugeRoot.label
font.pixelSize: gaugeRoot.labelSize
font.family: SettingsData.monoFontFamily
font.weight: Font.Bold
color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
}
StyledText {
text: gaugeRoot.sublabel
font.pixelSize: gaugeRoot.sublabelSize
font.weight: Font.Medium
color: gaugeRoot.accentColor
anchors.horizontalCenter: parent.horizontalCenter
}
StyledText {
text: gaugeRoot.detail
font.pixelSize: gaugeRoot.detailSize
font.family: SettingsData.monoFontFamily
color: gaugeRoot.detailColor
anchors.horizontalCenter: parent.horizontalCenter
visible: gaugeRoot.detail.length > 0
}
}
}
}