1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2025-12-07 14:05:38 -05:00

Add system resource dropdown

This commit is contained in:
bbedward
2025-07-13 15:36:41 -04:00
parent a9b5e0b09d
commit a1b6c9e791
13 changed files with 965 additions and 15 deletions

View File

@@ -12,7 +12,7 @@ Specifically created for [Niri](https://github.com/YaLTeR/niri).
```bash
# Arch
paru -S quickshell-git nerd-fonts ttf-material-symbols-variable-git matugen cliphist cava
paru -S quickshell-git nerd-fonts ttf-material-symbols-variable-git matugen cliphist cava wl-clipboard
# Some dependencies are optional
# - cava for audio visualizer, without it music will just randomly visualize

View File

@@ -0,0 +1,173 @@
import QtQuick
import Quickshell
import Quickshell.Io
pragma Singleton
pragma ComponentBehavior: Bound
Singleton {
id: root
// Process list properties
property var processes: []
property bool isUpdating: false
property int processUpdateInterval: 3000
// Sorting options
property string sortBy: "cpu" // "cpu", "memory", "name", "pid"
property bool sortDescending: true
property int maxProcesses: 20
Component.onCompleted: {
console.log("ProcessMonitorService: Starting initialization...")
updateProcessList()
console.log("ProcessMonitorService: Initialization complete")
}
// Process monitoring with ps command
Process {
id: processListProcess
command: ["bash", "-c", "ps axo pid,ppid,pcpu,pmem,comm,cmd --sort=-pcpu | head -" + (root.maxProcesses + 1)]
running: false
stdout: StdioCollector {
onStreamFinished: {
if (text.trim()) {
const lines = text.trim().split('\n')
const newProcesses = []
// Skip header line
for (let i = 1; i < lines.length; i++) {
const line = lines[i].trim()
if (!line) continue
// Parse ps output: PID PPID %CPU %MEM COMMAND CMD
const parts = line.split(/\s+/)
if (parts.length >= 6) {
const pid = parseInt(parts[0])
const ppid = parseInt(parts[1])
const cpu = parseFloat(parts[2])
const memory = parseFloat(parts[3])
const command = parts[4]
const fullCmd = parts.slice(5).join(' ')
newProcesses.push({
pid: pid,
ppid: ppid,
cpu: cpu,
memory: memory,
command: command,
fullCommand: fullCmd,
displayName: command.length > 15 ? command.substring(0, 15) + "..." : command
})
}
}
root.processes = newProcesses
root.isUpdating = false
}
}
}
onExited: (exitCode) => {
root.isUpdating = false
if (exitCode !== 0) {
console.warn("Process list check failed with exit code:", exitCode)
}
}
}
// Process monitoring timer
Timer {
id: processTimer
interval: root.processUpdateInterval
running: true
repeat: true
onTriggered: {
updateProcessList()
}
}
// Public functions
function updateProcessList() {
if (!root.isUpdating) {
root.isUpdating = true
// Update sort command based on current sort option
let sortOption = ""
switch (root.sortBy) {
case "cpu":
sortOption = sortDescending ? "--sort=-pcpu" : "--sort=+pcpu"
break
case "memory":
sortOption = sortDescending ? "--sort=-pmem" : "--sort=+pmem"
break
case "name":
sortOption = sortDescending ? "--sort=-comm" : "--sort=+comm"
break
case "pid":
sortOption = sortDescending ? "--sort=-pid" : "--sort=+pid"
break
default:
sortOption = "--sort=-pcpu"
}
processListProcess.command = ["bash", "-c", "ps axo pid,ppid,pcpu,pmem,comm,cmd " + sortOption + " | head -" + (root.maxProcesses + 1)]
processListProcess.running = true
}
}
function setSortBy(newSortBy) {
if (newSortBy !== root.sortBy) {
root.sortBy = newSortBy
updateProcessList()
}
}
function toggleSortOrder() {
root.sortDescending = !root.sortDescending
updateProcessList()
}
function killProcess(pid) {
if (pid > 0) {
const killCmd = ["bash", "-c", "kill " + pid]
const killProcess = Qt.createQmlObject(`
import QtQuick
import Quickshell.Io
Process {
command: ${JSON.stringify(killCmd)}
running: true
onExited: (exitCode) => {
if (exitCode === 0) {
console.log("Process killed successfully:", ${pid})
} else {
console.warn("Failed to kill process:", ${pid}, "exit code:", exitCode)
}
destroy()
}
}
`, root)
}
}
function getProcessIcon(command) {
// Return appropriate Material Design icon for common processes
const cmd = command.toLowerCase()
if (cmd.includes("firefox") || cmd.includes("chrome") || cmd.includes("browser")) return "web"
if (cmd.includes("code") || cmd.includes("editor") || cmd.includes("vim")) return "code"
if (cmd.includes("terminal") || cmd.includes("bash") || cmd.includes("zsh")) return "terminal"
if (cmd.includes("music") || cmd.includes("audio") || cmd.includes("spotify")) return "music_note"
if (cmd.includes("video") || cmd.includes("vlc") || cmd.includes("mpv")) return "play_circle"
if (cmd.includes("systemd") || cmd.includes("kernel") || cmd.includes("kthread")) return "settings"
return "memory" // Default process icon
}
function formatCpuUsage(cpu) {
return cpu.toFixed(1) + "%"
}
function formatMemoryUsage(memory) {
return memory.toFixed(1) + "%"
}
}

View File

@@ -13,6 +13,9 @@ Singleton {
property string cpuModel: ""
property real cpuFrequency: 0.0
// Previous CPU stats for accurate calculation
property var prevCpuStats: [0, 0, 0, 0, 0, 0, 0, 0]
// Memory properties
property real memoryUsage: 0.0
property real totalMemory: 0.0
@@ -26,8 +29,8 @@ Singleton {
property real cpuTemperature: 0.0
// Update intervals
property int cpuUpdateInterval: 2000
property int memoryUpdateInterval: 3000
property int cpuUpdateInterval: 1000
property int memoryUpdateInterval: 2000
property int temperatureUpdateInterval: 5000
Component.onCompleted: {
@@ -63,16 +66,33 @@ Singleton {
}
}
// CPU usage monitoring
// CPU usage monitoring with accurate calculation
Process {
id: cpuUsageProcess
command: ["bash", "-c", "grep 'cpu ' /proc/stat | awk '{usage=($2+$4)*100/($2+$3+$4+$5)} END {printf \"%.1f\", usage}'"]
command: ["bash", "-c", "head -1 /proc/stat | awk '{print $2,$3,$4,$5,$6,$7,$8,$9}'"]
running: false
stdout: StdioCollector {
onStreamFinished: {
if (text.trim()) {
root.cpuUsage = parseFloat(text.trim())
const stats = text.trim().split(" ").map(x => parseInt(x))
if (root.prevCpuStats[0] > 0) {
// Calculate differences
let diffs = []
for (let i = 0; i < 8; i++) {
diffs[i] = stats[i] - root.prevCpuStats[i]
}
// Calculate total and idle time
const totalTime = diffs.reduce((a, b) => a + b, 0)
const idleTime = diffs[3] + diffs[4] // idle + iowait
// CPU usage percentage
if (totalTime > 0) {
root.cpuUsage = Math.max(0, Math.min(100, ((totalTime - idleTime) / totalTime) * 100))
}
}
root.prevCpuStats = stats
}
}
}

View File

@@ -9,6 +9,7 @@ Singleton {
property var weather: ({
available: false,
loading: true,
temp: 0,
tempF: 0,
city: "",
@@ -104,6 +105,7 @@ Singleton {
root.weather = {
available: true,
loading: false,
temp: Number(current.temp_C) || 0,
tempF: Number(current.temp_F) || 0,
city: location.areaName?.[0]?.value || "Unknown",
@@ -122,6 +124,7 @@ Singleton {
} catch (e) {
console.warn("Failed to parse weather data:", e.message)
root.weather.available = false
root.weather.loading = false
}
}
}
@@ -130,6 +133,7 @@ Singleton {
if (exitCode !== 0) {
console.warn("Weather fetch failed with exit code:", exitCode)
root.weather.available = false
root.weather.loading = false
}
}
}

View File

@@ -8,6 +8,7 @@ singleton BluetoothService 1.0 BluetoothService.qml
singleton BrightnessService 1.0 BrightnessService.qml
singleton BatteryService 1.0 BatteryService.qml
singleton SystemMonitorService 1.0 SystemMonitorService.qml
singleton ProcessMonitorService 1.0 ProcessMonitorService.qml
singleton AppSearchService 1.0 AppSearchService.qml
singleton LauncherService 1.0 LauncherService.qml
singleton NiriWorkspaceService 1.0 NiriWorkspaceService.qml

View File

@@ -41,7 +41,7 @@ Rectangle {
Column {
anchors.centerIn: parent
spacing: theme.spacingS
visible: !weather || !weather.available
visible: !weather || !weather.available || weather.temp === 0
Text {
text: "cloud_off"
@@ -63,7 +63,7 @@ Rectangle {
Row {
anchors.fill: parent
spacing: theme.spacingL
visible: weather
visible: weather && weather.available && weather.temp !== 0
// Weather icon
Text {
@@ -108,7 +108,7 @@ Rectangle {
columns: 2
spacing: theme.spacingM
anchors.horizontalCenter: parent.horizontalCenter
visible: weather !== null
visible: weather && weather.available && weather.temp !== 0
Row {
spacing: theme.spacingXS

View File

@@ -2,12 +2,14 @@ import QtQuick
import QtQuick.Controls
import "../Common"
import "../Services"
import "."
Rectangle {
id: cpuWidget
property bool showPercentage: true
property bool showIcon: true
property var processDropdown: null
width: 55
height: 30
@@ -24,9 +26,13 @@ Rectangle {
id: cpuArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
// CPU widget clicked
if (processDropdown) {
ProcessMonitorService.setSortBy("cpu")
processDropdown.toggle()
}
}
}

View File

@@ -0,0 +1,729 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Effects
import QtQuick.Layouts
import Quickshell
import Quickshell.Widgets
import Quickshell.Wayland
import Quickshell.Io
import "../Common"
import "../Services"
PanelWindow {
id: processDropdown
property bool isVisible: false
property var parentWidget: null
visible: isVisible
implicitWidth: 500
implicitHeight: 500
WlrLayershell.layer: WlrLayershell.Overlay
WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
color: "transparent"
anchors {
top: true
left: true
right: true
bottom: true
}
// Click outside to close
MouseArea {
anchors.fill: parent
onClicked: processDropdown.hide()
}
Rectangle {
id: dropdownContent
width: Math.min(500, parent.width - Theme.spacingL * 2)
height: Math.min(500, parent.height - Theme.barHeight - Theme.spacingS * 2)
x: Math.max(Theme.spacingL, parent.width - width - Theme.spacingL)
y: Theme.barHeight + Theme.spacingXS
radius: Theme.cornerRadiusLarge
color: Theme.surfaceContainer
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
border.width: 1
clip: true
opacity: processDropdown.isVisible ? 1.0 : 0.0
scale: processDropdown.isVisible ? 1.0 : 0.85
// Add shadow effect
layer.enabled: true
layer.effect: MultiEffect {
shadowEnabled: true
shadowHorizontalOffset: 0
shadowVerticalOffset: 8
shadowBlur: 1.0
shadowColor: Qt.rgba(0, 0, 0, 0.15)
shadowOpacity: processDropdown.isVisible ? 0.15 : 0
}
// Smooth animations
Behavior on opacity {
NumberAnimation {
duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing
}
}
Behavior on scale {
NumberAnimation {
duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing
}
}
// Click inside dropdown - consume the event
MouseArea {
anchors.fill: parent
onClicked: {
// Consume clicks inside dropdown to prevent closing
}
}
ColumnLayout {
anchors.fill: parent
anchors.margins: Theme.spacingL
spacing: Theme.spacingM
// Header
Column {
Layout.fillWidth: true
spacing: Theme.spacingM
Row {
width: parent.width
height: 32
Text {
id: processTitle
text: "System Processes"
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
Item {
width: parent.width - processTitle.width - sortControls.width - Theme.spacingM
height: 1
}
// Sort controls
Row {
id: sortControls
spacing: Theme.spacingXS
anchors.verticalCenter: parent.verticalCenter
Rectangle {
width: cpuButton.width + ramButton.width + Theme.spacingXS
height: 28
radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
Row {
anchors.centerIn: parent
spacing: 0
Button {
id: cpuButton
text: "CPU"
flat: true
checkable: true
checked: ProcessMonitorService.sortBy === "cpu"
onClicked: ProcessMonitorService.setSortBy("cpu")
font.pixelSize: Theme.fontSizeSmall
hoverEnabled: true
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: parent.clicked()
}
contentItem: Text {
text: parent.text
font: parent.font
color: parent.checked ? Theme.primary : Theme.surfaceText
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
background: Rectangle {
color: parent.checked ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
radius: Theme.cornerRadius
Behavior on color {
ColorAnimation { duration: Theme.shortDuration }
}
}
}
Button {
id: ramButton
text: "RAM"
flat: true
checkable: true
checked: ProcessMonitorService.sortBy === "memory"
onClicked: ProcessMonitorService.setSortBy("memory")
font.pixelSize: Theme.fontSizeSmall
hoverEnabled: true
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: parent.clicked()
}
contentItem: Text {
text: parent.text
font: parent.font
color: parent.checked ? Theme.primary : Theme.surfaceText
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
background: Rectangle {
color: parent.checked ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
radius: Theme.cornerRadius
Behavior on color {
ColorAnimation { duration: Theme.shortDuration }
}
}
}
}
}
Rectangle {
width: 28
height: 28
radius: Theme.cornerRadius
color: sortOrderArea.containsMouse ?
Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08) :
"transparent"
Text {
text: ProcessMonitorService.sortDescending ? "↓" : "↑"
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
anchors.centerIn: parent
}
MouseArea {
id: sortOrderArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: ProcessMonitorService.toggleSortOrder()
}
Behavior on color {
ColorAnimation { duration: Theme.shortDuration }
}
}
}
}
// Separator
Rectangle {
width: parent.width
height: 1
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
}
}
// Headers
Row {
id: columnHeaders
Layout.fillWidth: true
Text {
text: "Process"
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
color: Theme.surfaceText
opacity: 0.7
width: 180
}
Text {
text: "CPU"
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
color: Theme.surfaceText
opacity: 0.7
width: 60
horizontalAlignment: Text.AlignRight
}
Text {
text: "RAM"
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
color: Theme.surfaceText
opacity: 0.7
width: 60
horizontalAlignment: Text.AlignRight
}
Text {
text: "PID"
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
color: Theme.surfaceText
opacity: 0.7
width: 60
horizontalAlignment: Text.AlignRight
}
}
// Process list
ScrollView {
Layout.fillWidth: true
Layout.fillHeight: true
Layout.minimumHeight: 200
clip: true
ScrollBar.vertical.policy: ScrollBar.AsNeeded
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
ListView {
id: processListView
anchors.fill: parent
model: ProcessMonitorService.processes
spacing: 2
delegate: Rectangle {
width: processListView.width - 16
height: 36
radius: Theme.cornerRadius
color: processMouseArea.containsMouse ?
Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) :
"transparent"
MouseArea {
id: processMouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: (mouse) => {
if (mouse.button === Qt.RightButton) {
if (modelData && modelData.pid > 0) {
processContextMenuWindow.processData = modelData
let globalPos = processMouseArea.mapToGlobal(mouse.x, mouse.y)
processContextMenuWindow.show(globalPos.x, globalPos.y)
}
}
}
onPressAndHold: {
// Context menu for kill process etc
if (modelData && modelData.pid > 0) {
processContextMenuWindow.processData = modelData
let globalPos = processMouseArea.mapToGlobal(processMouseArea.width / 2, processMouseArea.height / 2)
processContextMenuWindow.show(globalPos.x, globalPos.y)
}
}
}
Row {
anchors.left: parent.left
anchors.leftMargin: 8
anchors.verticalCenter: parent.verticalCenter
spacing: 8
width: parent.width - 16
// Process icon
Text {
text: ProcessMonitorService.getProcessIcon(modelData ? modelData.command : "")
font.family: Theme.iconFont
font.pixelSize: Theme.iconSize - 4
color: {
if (modelData && modelData.cpu > 80) return Theme.error
if (modelData && modelData.cpu > 50) return Theme.warning
return Theme.surfaceText
}
opacity: 0.8
anchors.verticalCenter: parent.verticalCenter
}
// Process name
Text {
text: modelData ? modelData.displayName : ""
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
width: 150
elide: Text.ElideRight
anchors.verticalCenter: parent.verticalCenter
}
Item { width: parent.width - 280 }
// CPU usage
Text {
text: ProcessMonitorService.formatCpuUsage(modelData ? modelData.cpu : 0)
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
color: {
if (modelData && modelData.cpu > 80) return Theme.error
if (modelData && modelData.cpu > 50) return Theme.warning
return Theme.surfaceText
}
width: 60
horizontalAlignment: Text.AlignRight
anchors.verticalCenter: parent.verticalCenter
}
// Memory usage
Text {
text: ProcessMonitorService.formatMemoryUsage(modelData ? modelData.memory : 0)
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
color: {
if (modelData && modelData.memory > 10) return Theme.error
if (modelData && modelData.memory > 5) return Theme.warning
return Theme.surfaceText
}
width: 60
horizontalAlignment: Text.AlignRight
anchors.verticalCenter: parent.verticalCenter
}
// PID
Text {
text: modelData ? modelData.pid.toString() : ""
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
opacity: 0.7
width: 60
horizontalAlignment: Text.AlignRight
anchors.verticalCenter: parent.verticalCenter
}
}
}
}
}
}
}
// Styled context menu for process actions - positioned in global coordinates
PanelWindow {
id: processContextMenuWindow
property var processData: null
property bool menuVisible: false
visible: menuVisible
color: "transparent"
WlrLayershell.layer: WlrLayershell.Overlay
WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
anchors {
top: true
left: true
right: true
bottom: true
}
Rectangle {
id: processContextMenu
width: 180
height: menuColumn.implicitHeight + Theme.spacingS * 2
radius: Theme.cornerRadiusLarge
color: Theme.surfaceContainer
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
border.width: 1
// Material 3 drop shadow
Rectangle {
anchors.fill: parent
anchors.topMargin: 4
anchors.leftMargin: 2
anchors.rightMargin: -2
anchors.bottomMargin: -4
radius: parent.radius
color: Qt.rgba(0, 0, 0, 0.15)
z: parent.z - 1
}
// Material 3 animations
opacity: menuVisible ? 1.0 : 0.0
scale: menuVisible ? 1.0 : 0.85
Behavior on opacity {
NumberAnimation {
duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing
}
}
Behavior on scale {
NumberAnimation {
duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing
}
}
Column {
id: menuColumn
anchors.fill: parent
anchors.margins: Theme.spacingS
spacing: 1
// Copy PID
Rectangle {
width: parent.width
height: 28
radius: Theme.cornerRadiusSmall
color: copyPidArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
Text {
anchors.left: parent.left
anchors.leftMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
text: "Copy PID"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
font.weight: Font.Normal
}
MouseArea {
id: copyPidArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
if (processContextMenuWindow.processData) {
copyPidProcess.command = ["wl-copy", processContextMenuWindow.processData.pid.toString()]
copyPidProcess.running = true
}
processContextMenuWindow.hide()
}
}
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
}
// Copy Process Name
Rectangle {
width: parent.width
height: 28
radius: Theme.cornerRadiusSmall
color: copyNameArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
Text {
anchors.left: parent.left
anchors.leftMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
text: "Copy Process Name"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
font.weight: Font.Normal
}
MouseArea {
id: copyNameArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
if (processContextMenuWindow.processData) {
let processName = processContextMenuWindow.processData.displayName || processContextMenuWindow.processData.command
copyNameProcess.command = ["wl-copy", processName]
copyNameProcess.running = true
}
processContextMenuWindow.hide()
}
}
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
}
// Separator
Rectangle {
width: parent.width - Theme.spacingS * 2
height: 5
anchors.horizontalCenter: parent.horizontalCenter
color: "transparent"
Rectangle {
anchors.centerIn: parent
width: parent.width
height: 1
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
}
}
// Kill Process
Rectangle {
width: parent.width
height: 28
radius: Theme.cornerRadiusSmall
color: killArea.containsMouse ? Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12) : "transparent"
enabled: processContextMenuWindow.processData && processContextMenuWindow.processData.pid > 1000
opacity: enabled ? 1.0 : 0.5
Text {
anchors.left: parent.left
anchors.leftMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
text: "Kill Process"
font.pixelSize: Theme.fontSizeSmall
color: parent.enabled ? (killArea.containsMouse ? Theme.error : Theme.surfaceText) : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5)
font.weight: Font.Normal
}
MouseArea {
id: killArea
anchors.fill: parent
hoverEnabled: true
cursorShape: parent.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
enabled: parent.enabled
onClicked: {
if (processContextMenuWindow.processData) {
killProcess.command = ["kill", processContextMenuWindow.processData.pid.toString()]
killProcess.running = true
}
processContextMenuWindow.hide()
}
}
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
}
// Force Kill Process
Rectangle {
width: parent.width
height: 28
radius: Theme.cornerRadiusSmall
color: forceKillArea.containsMouse ? Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12) : "transparent"
enabled: processContextMenuWindow.processData && processContextMenuWindow.processData.pid > 1000
opacity: enabled ? 1.0 : 0.5
Text {
anchors.left: parent.left
anchors.leftMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
text: "Force Kill Process"
font.pixelSize: Theme.fontSizeSmall
color: parent.enabled ? (forceKillArea.containsMouse ? Theme.error : Theme.surfaceText) : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5)
font.weight: Font.Normal
}
MouseArea {
id: forceKillArea
anchors.fill: parent
hoverEnabled: true
cursorShape: parent.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
enabled: parent.enabled
onClicked: {
if (processContextMenuWindow.processData) {
forceKillProcess.command = ["kill", "-9", processContextMenuWindow.processData.pid.toString()]
forceKillProcess.running = true
}
processContextMenuWindow.hide()
}
}
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
}
}
}
function show(x, y) {
processContextMenu.x = x
processContextMenu.y = y
processContextMenuWindow.menuVisible = true
}
function hide() {
processContextMenuWindow.menuVisible = false
}
// Click outside to close
MouseArea {
anchors.fill: parent
z: -1
onClicked: {
processContextMenuWindow.menuVisible = false
}
}
// Process objects for commands
Process {
id: copyPidProcess
running: false
}
Process {
id: copyNameProcess
running: false
}
Process {
id: killProcess
running: false
}
Process {
id: forceKillProcess
running: false
}
}
// Close dropdown when clicking outside
function hide() {
isVisible = false
}
function show() {
isVisible = true
ProcessMonitorService.updateProcessList()
}
function toggle() {
if (isVisible) {
hide()
} else {
show()
}
}
}

View File

@@ -2,12 +2,14 @@ import QtQuick
import QtQuick.Controls
import "../Common"
import "../Services"
import "."
Rectangle {
id: ramWidget
property bool showPercentage: true
property bool showIcon: true
property var processDropdown: null
width: 55
height: 30
@@ -24,9 +26,13 @@ Rectangle {
id: ramArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
// RAM widget clicked
if (processDropdown) {
ProcessMonitorService.setSortBy("memory")
processDropdown.toggle()
}
}
}
@@ -57,5 +63,4 @@ Rectangle {
anchors.verticalCenter: parent.verticalCenter
}
}
}

View File

@@ -50,6 +50,9 @@ PanelWindow {
// Notification properties
property int notificationCount: 0
// Process dropdown reference
property var processDropdown: null
// Clipboard properties
signal clipboardRequested()
@@ -139,6 +142,7 @@ PanelWindow {
anchors.rightMargin: Theme.spacingM
anchors.topMargin: Theme.spacingXS
anchors.bottomMargin: Theme.spacingXS
clip: true
Row {
id: leftSection
@@ -203,7 +207,7 @@ PanelWindow {
weatherCode: topBar.weatherCode
weatherTemp: topBar.weatherTemp
weatherTempF: topBar.weatherTempF
visible: Prefs.showWeather
visible: Prefs.showWeather && topBar.weatherAvailable && topBar.weatherTemp > 0 && topBar.weatherTempF > 0
onClicked: {
if (topBar.shellRoot) {
@@ -281,11 +285,13 @@ PanelWindow {
CpuMonitorWidget {
anchors.verticalCenter: parent.verticalCenter
visible: Prefs.showSystemResources
processDropdown: topBar.processDropdown
}
RamMonitorWidget {
anchors.verticalCenter: parent.verticalCenter
visible: Prefs.showSystemResources
processDropdown: topBar.processDropdown
}
NotificationCenterButton {

View File

@@ -12,8 +12,8 @@ Rectangle {
signal clicked()
visible: weatherAvailable
width: weatherAvailable ? Math.min(100, weatherRow.implicitWidth + Theme.spacingS * 2) : 0
// Visibility is now controlled by TopBar.qml
width: visible ? Math.min(100, weatherRow.implicitWidth + Theme.spacingS * 2) : 0
height: 30
radius: Theme.cornerRadius
color: weatherArea.containsMouse ?

View File

@@ -14,6 +14,7 @@ PowerConfirmDialog 1.0 PowerConfirmDialog.qml
ThemePicker 1.0 ThemePicker.qml
CpuMonitorWidget 1.0 CpuMonitorWidget.qml
RamMonitorWidget 1.0 RamMonitorWidget.qml
ProcessListDropdown 1.0 ProcessListDropdown.qml
SpotlightLauncher 1.0 SpotlightLauncher.qml
SettingsPopup 1.0 SettingsPopup.qml
SettingsSection 1.0 SettingsSection.qml

View File

@@ -304,6 +304,7 @@ ShellRoot {
bluetoothEnabled: root.bluetoothEnabled
shellRoot: root
notificationCount: notificationHistory.count
processDropdown: processListDropdown
// Connect tray menu properties
showTrayMenu: root.showTrayMenu
@@ -340,6 +341,10 @@ ShellRoot {
PowerMenuPopup {}
PowerConfirmDialog {}
ProcessListDropdown {
id: processListDropdown
}
SettingsPopup {
id: settingsPopup
settingsVisible: root.settingsVisible