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 ```bash
# Arch # 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 # Some dependencies are optional
# - cava for audio visualizer, without it music will just randomly visualize # - 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 string cpuModel: ""
property real cpuFrequency: 0.0 property real cpuFrequency: 0.0
// Previous CPU stats for accurate calculation
property var prevCpuStats: [0, 0, 0, 0, 0, 0, 0, 0]
// Memory properties // Memory properties
property real memoryUsage: 0.0 property real memoryUsage: 0.0
property real totalMemory: 0.0 property real totalMemory: 0.0
@@ -26,8 +29,8 @@ Singleton {
property real cpuTemperature: 0.0 property real cpuTemperature: 0.0
// Update intervals // Update intervals
property int cpuUpdateInterval: 2000 property int cpuUpdateInterval: 1000
property int memoryUpdateInterval: 3000 property int memoryUpdateInterval: 2000
property int temperatureUpdateInterval: 5000 property int temperatureUpdateInterval: 5000
Component.onCompleted: { Component.onCompleted: {
@@ -63,16 +66,33 @@ Singleton {
} }
} }
// CPU usage monitoring // CPU usage monitoring with accurate calculation
Process { Process {
id: cpuUsageProcess 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 running: false
stdout: StdioCollector { stdout: StdioCollector {
onStreamFinished: { onStreamFinished: {
if (text.trim()) { 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: ({ property var weather: ({
available: false, available: false,
loading: true,
temp: 0, temp: 0,
tempF: 0, tempF: 0,
city: "", city: "",
@@ -104,6 +105,7 @@ Singleton {
root.weather = { root.weather = {
available: true, available: true,
loading: false,
temp: Number(current.temp_C) || 0, temp: Number(current.temp_C) || 0,
tempF: Number(current.temp_F) || 0, tempF: Number(current.temp_F) || 0,
city: location.areaName?.[0]?.value || "Unknown", city: location.areaName?.[0]?.value || "Unknown",
@@ -122,6 +124,7 @@ Singleton {
} catch (e) { } catch (e) {
console.warn("Failed to parse weather data:", e.message) console.warn("Failed to parse weather data:", e.message)
root.weather.available = false root.weather.available = false
root.weather.loading = false
} }
} }
} }
@@ -130,6 +133,7 @@ Singleton {
if (exitCode !== 0) { if (exitCode !== 0) {
console.warn("Weather fetch failed with exit code:", exitCode) console.warn("Weather fetch failed with exit code:", exitCode)
root.weather.available = false 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 BrightnessService 1.0 BrightnessService.qml
singleton BatteryService 1.0 BatteryService.qml singleton BatteryService 1.0 BatteryService.qml
singleton SystemMonitorService 1.0 SystemMonitorService.qml singleton SystemMonitorService 1.0 SystemMonitorService.qml
singleton ProcessMonitorService 1.0 ProcessMonitorService.qml
singleton AppSearchService 1.0 AppSearchService.qml singleton AppSearchService 1.0 AppSearchService.qml
singleton LauncherService 1.0 LauncherService.qml singleton LauncherService 1.0 LauncherService.qml
singleton NiriWorkspaceService 1.0 NiriWorkspaceService.qml singleton NiriWorkspaceService 1.0 NiriWorkspaceService.qml

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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