Files
asepharyana 1cdb82a76f Sync config from arch
- hypr/apps.lua
- hypr/autostart.lua
- hypr/envs.lua
- hypr/hyprland.lua
- hypr/hyprsunset.conf
- hypr/input.lua
- hypr/looknfeel.lua
- hypr/omasettings.lua
- hypr/xdph.conf
- omarchy/branding/about.txt
- omarchy/branding/screensaver.txt
- omarchy/extensions/omarchy-menu.jsonc
- omarchy/hooks/battery-low.d/play-warning-sound.sample
- omarchy/hooks/font-set.d/show-font-notification.sample
- omarchy/hooks/post-boot.d/weather.sample
- omarchy/hooks/post-update.d/install-voxtype.hook
- omarchy/hooks/post-update.d/setup-agent.hook
- omarchy/hooks/post-update.d/setup-fingerprint.hook
- omarchy/hooks/post-update.d/show-update-notification.sample
- omarchy/hooks/pre-refresh-pacman.d/add-custom-repo.sample
- omarchy/hooks/theme-set.d/show-theme-notification.sample
- omarchy/shell.json
- omarchy/shell.toml
- omarchy/theme.name
- omarchy/themes/azure-glow/README.md
- omarchy/themes/azure-glow/alacritty.toml
- omarchy/themes/azure-glow/btop.theme
- omarchy/themes/azure-glow/hyprland.conf
- omarchy/themes/azure-glow/hyprlock.conf
- omarchy/themes/azure-glow/icons.theme
- … 269 more
2026-09-23 15:19:12 +07:00

1809 lines
59 KiB
QML

import QtQuick
import QtQuick.Controls
import qs.Commons
import qs.Ui
import "Model.js" as Model
Panel {
id: root
moduleName: "stappmus.activity-monitor"
ipcTarget: "stappmus.activity-monitor"
implicitWidth: button.implicitWidth
implicitHeight: button.implicitHeight
property bool expanded: false
property bool settingsOpen: false
property bool settingsControlActive: false
property bool hintsVisible: false
property bool cursorActive: false
property int selectedProcessIndex: 0
property string selectedProcessKey: ""
property string processQuery: ""
property string sortKey: "cpu"
property bool sortAscending: false
property Item searchField: null
property var processViewport: null
readonly property color foreground: Color.popups.text
readonly property color accent: Color.accent
readonly property color urgent: Color.urgent
readonly property color dim: Color.muted
readonly property var snapshot: activity.snapshot
readonly property var storageSnapshot: activity.storageSnapshot
readonly property var gpuSnapshot: activity.gpuSnapshot
readonly property var gpus: activity.gpus
readonly property var processPower: activity.processPower
readonly property var processes: activity.processes
readonly property var metrics: activity.metrics
readonly property bool processMetricsReady: activity.processMetricsReady
readonly property bool processPowerUnavailable: activity.processPowerUnavailable
readonly property bool hasSnapshot: snapshot.sample > 0
readonly property int processPidColumnWidth: Style.space(54)
readonly property int processUserColumnWidth: Style.space(96)
readonly property int processMetricColumnWidth: Style.space(66)
readonly property bool showProcessUserColumn: panel.contentWidth >= Style.space(650)
readonly property bool showProcessTimeColumn: panel.contentWidth >= Style.space(560)
readonly property string processPowerTooltip: !powerEstimatesEnabled
? "Process power estimates are disabled in Activity settings"
: (processPowerUnavailable
? "CPU package energy is unavailable on this system"
: (processPower.available
? "Estimated CPU-time share of measured CPU package power"
: "Measuring CPU package energy…"))
readonly property var cpuTemperature: Model.cpuTemperature(activity.thermalSnapshot.temperatures)
property string selectedDiskId: "all"
property string selectedStoragePath: ""
property bool storageSelectionPinned: false
readonly property var diskItems: Model.diskCycleItems(metrics)
readonly property var storageItems: Model.storageVolumes(storageSnapshot)
readonly property var activeDisk: Model.selectDiskItem(diskItems, selectedDiskId)
readonly property var activeStorage: Model.selectStorageVolume(
storageItems,
selectedStoragePath,
storageSelectionPinned
)
readonly property real storageUsedFraction: Model.storageUsedFraction(activeStorage)
readonly property bool canCycleDisks: diskItems.length > 1
readonly property bool canCycleStorage: storageItems.length > 1
readonly property int dieGapCompact: Style.space(2)
readonly property int dieGapExpanded: Style.space(3)
readonly property var corePaintSizesCompact: ({
gap: dieGapCompact,
domainGap: Style.space(4),
pad: Style.space(3),
performance: Style.space(10),
efficiency: Style.space(7),
same: Style.space(7)
})
readonly property var corePaintSizesExpanded: ({
gap: dieGapExpanded,
domainGap: Style.space(6),
pad: Style.space(5),
performance: Style.space(16),
efficiency: Style.space(11),
same: Style.space(11)
})
readonly property var corePaintCompact: expanded
? Model.emptyCorePaint()
: Model.paintCoreLayout(metrics.cores, snapshot.topology, corePaintSizesCompact, "compact")
readonly property var corePaintExpanded: expanded
? Model.paintCoreLayout(metrics.cores, snapshot.topology, corePaintSizesExpanded, "expanded")
: Model.emptyCorePaint()
readonly property var sortedProcesses: expanded
? Model.filterAndSortProcesses(processes, processQuery, sortKey, sortAscending)
: []
readonly property var topProcesses: expanded
? []
: Model.filterAndSortProcesses(processes, "", "cpu", false).slice(0, 3)
readonly property var selectedProcess: sortedProcesses.length > 0
? sortedProcesses[Math.max(0, Math.min(selectedProcessIndex, sortedProcesses.length - 1))]
: null
readonly property string samplingSpeedSetting: normalizedSamplingSpeed(setting("samplingSpeed", "Balanced"))
readonly property int historySamplesSetting: Math.max(20, Math.min(240,
Math.round(Number(setting("historySamples", 60)) || 60)))
readonly property string temperatureUnitSetting: normalizedTemperatureUnit(setting("temperatureUnit", "Celsius"))
readonly property bool showFrequencies: booleanSetting("showFrequencies", true)
readonly property bool openExpandedByDefault: booleanSetting("openExpanded", false)
readonly property bool powerEstimatesEnabled: booleanSetting("processPowerEnabled", true)
function refresh() {
activity.refresh()
}
function setExpanded(value) {
if (!value && processActions.confirmationOpen) processActions.cancel()
expanded = value
cursorActive = false
selectedProcessIndex = 0
selectedProcessKey = ""
if (!expanded) {
processQuery = ""
hintsVisible = false
}
if (!expanded && sortKey === "power") {
sortKey = "cpu"
sortAscending = false
}
Qt.callLater(function() {
if (root.opened && keyCatcher) keyCatcher.forceActiveFocus()
})
}
function setSettingsOpen(value) {
settingsOpen = value
settingsControlActive = value
if (!value) {
Qt.callLater(function() {
if (root.opened && keyCatcher) keyCatcher.forceActiveFocus()
})
}
}
function clampProcessCursor() {
if (sortedProcesses.length === 0) {
selectedProcessIndex = 0
selectedProcessKey = ""
return
}
selectedProcessIndex = Math.max(0, Math.min(selectedProcessIndex, sortedProcesses.length - 1))
selectedProcessKey = Model.processIdentityKey(sortedProcesses[selectedProcessIndex])
}
function restoreProcessCursor() {
if (sortedProcesses.length === 0) {
selectedProcessIndex = 0
selectedProcessKey = ""
return
}
if (selectedProcessKey) {
for (var i = 0; i < sortedProcesses.length; i++) {
if (Model.processIdentityKey(sortedProcesses[i]) === selectedProcessKey) {
selectedProcessIndex = i
return
}
}
}
clampProcessCursor()
}
function selectProcess(index) {
if (sortedProcesses.length === 0) return
selectedProcessIndex = Math.max(0, Math.min(sortedProcesses.length - 1, index))
selectedProcessKey = Model.processIdentityKey(sortedProcesses[selectedProcessIndex])
}
function moveProcess(delta) {
if (!expanded || sortedProcesses.length === 0) return
cursorActive = true
selectProcess(selectedProcessIndex + delta)
pointerGate.reset()
Qt.callLater(function() {
if (root.processViewport && root.processViewport.ensureSelected) root.processViewport.ensureSelected()
})
}
function setSort(nextKey) {
if (nextKey === "power" && (!powerEstimatesEnabled || !processPower.available)) return
if (sortKey === nextKey) sortAscending = !sortAscending
else {
sortKey = nextKey
sortAscending = nextKey === "name"
}
}
function cycleSort(delta) {
var keys = powerEstimatesEnabled && processPower.available
? ["cpu", "memory", "power", "pid", "runtime", "name"]
: ["cpu", "memory", "pid", "runtime", "name"]
var index = keys.indexOf(sortKey)
if (index < 0) index = 0
index = (index + delta + keys.length) % keys.length
setSort(keys[index])
}
function focusSearch() {
if (!expanded || !searchField) return
searchField.forceActiveFocus()
searchField.selectAll()
}
function finishSearch() {
if (searchField) searchField.focus = false
keyCatcher.forceActiveFocus()
}
function percentText(value) {
return Math.round(Number(value) || 0) + "%"
}
function booleanSetting(name, fallback) {
var value = setting(name, fallback)
if (typeof value === "boolean") return value
if (typeof value === "string") {
var normalized = value.trim().toLowerCase()
if (normalized === "true" || normalized === "on" || normalized === "yes") return true
if (normalized === "false" || normalized === "off" || normalized === "no") return false
}
return value === undefined || value === null ? fallback : !!value
}
function normalizedSamplingSpeed(value) {
return Model.samplingProfile(value).name
}
function normalizedTemperatureUnit(value) {
return String(value || "Celsius").toLowerCase() === "fahrenheit"
? "Fahrenheit"
: "Celsius"
}
function persistSettings(values) {
var entry = { id: root.moduleName }
var current = root.settings || ({})
for (var existing in current) if (existing !== "id") entry[existing] = current[existing]
for (var key in values) entry[key] = values[key]
if (values.samplingSpeed !== undefined) {
delete entry.refreshIntervalMs
delete entry.processRefreshIntervalMs
delete entry.thermalRefreshIntervalMs
delete entry.gpuRefreshIntervalMs
delete entry.storageRefreshIntervalMs
}
root.settings = entry
if (root.bar && root.bar.shell && typeof root.bar.shell.updateEntryInline === "function")
root.bar.shell.updateEntryInline(root.moduleName, entry)
}
function resetPreferences() {
persistSettings({
samplingSpeed: "Balanced",
historySamples: 60,
temperatureUnit: "Celsius",
showFrequencies: true,
openExpanded: false,
processPowerEnabled: true
})
}
function swapBadgeText() {
if (!hasSnapshot || !snapshot.memory.swapTotal) return ""
return "Swap " + percentText(metrics.swap)
}
function memoryBreakdownText() {
if (!snapshot.memory.total) return "used — · cache —"
var usedText = Model.formatBytes(snapshot.memory.total - snapshot.memory.available)
var cacheText = Model.formatBytes(snapshot.memory.cached)
var usedSeparator = usedText.lastIndexOf(" ")
var cacheSeparator = cacheText.lastIndexOf(" ")
var usedUnit = usedSeparator >= 0 ? usedText.slice(usedSeparator + 1) : ""
var cacheUnit = cacheSeparator >= 0 ? cacheText.slice(cacheSeparator + 1) : ""
if (usedUnit && usedUnit === cacheUnit) usedText = usedText.slice(0, usedSeparator)
return "used " + usedText + " · cache " + cacheText
}
function cpuCardDetail() {
var parts = []
if (cpuTemperature)
parts.push(Model.formatTemperature(cpuTemperature.value, temperatureUnitSetting))
if (powerEstimatesEnabled && processPower.available) {
var watts = measuredPackagePowerText(processPower.watts)
if (watts) parts.push(watts)
}
return parts.join(" · ")
}
function frequencyText(value, unit) {
if (!showFrequencies) return ""
var frequency = Number(value)
if (!isFinite(frequency) || frequency < 0) return ""
return Math.round(frequency) + " " + unit
}
function gpuFrequencyText(gpu) {
if (!showFrequencies) return ""
var frequency = Number(gpu && gpu.frequencyMHz)
if (!isFinite(frequency) || frequency < 0) return ""
if (frequency === 0) return "IDLE"
return Math.round(frequency) + " MHz"
}
function cpuCardLabel() {
var frequency = frequencyText(snapshot.cpuFrequencyMHz, "MHz")
return frequency ? "CPU · " + frequency : "CPU"
}
function memoryCardLabel() {
var speed = frequencyText(snapshot.memorySpeedMTs, "MT/s")
return speed ? "RAM · " + speed : "RAM USAGE"
}
function compactGpuName(gpu) {
var name = String(gpu && gpu.name || "GPU")
name = name.replace(/^Intel Corporation[ ]*/i, "")
.replace(/^NVIDIA Corporation[ ]*/i, "")
.replace(/^NVIDIA[ ]*/i, "")
.replace(/^Advanced Micro Devices[^]]*\][ ]*/i, "")
.replace(/^AMD[ ]*/i, "")
return name || "GPU"
}
function gpuHeadingText() {
var adapters = Array.isArray(gpus) ? gpus : []
if (adapters.length === 0) return "GPU"
if (adapters.length > 1) return "GPU · " + adapters.length + " adapters"
var name = compactGpuName(adapters[0])
var bracketed = name.match(/\[([^\]]+)\]/g)
if (bracketed && bracketed.length > 0)
name = bracketed[bracketed.length - 1].slice(1, -1)
var frequency = gpuFrequencyText(adapters[0])
return "GPU · " + name + (frequency ? " · " + frequency : "")
}
function gpuUsageText(gpu) {
var usage = Number(gpu && gpu.usage)
return isFinite(usage) && usage >= 0 ? Math.round(usage) + "%" : "Sampling"
}
function gpuUsageAndClockText(gpu) {
var usage = gpuUsageText(gpu)
var frequency = gpuFrequencyText(gpu)
return frequency ? usage + " · " + frequency : usage
}
function gpuMemoryLabel(gpu) {
if (gpu && gpu.memoryKind === "shared") return "Shared"
if (gpu && gpu.memoryKind === "vram") return "VRAM"
return "Memory"
}
function gpuMemoryText(gpu) {
var used = Number(gpu && gpu.memoryUsed)
var total = Number(gpu && gpu.memoryTotal)
if (!isFinite(used) || used < 0) return "Unavailable"
var usedText = Model.formatBytes(used)
if (!isFinite(total) || total <= 0) return usedText
var totalText = Model.formatBytes(total)
var usedSeparator = usedText.lastIndexOf(" ")
var totalSeparator = totalText.lastIndexOf(" ")
if (usedSeparator >= 0 && totalSeparator >= 0
&& usedText.slice(usedSeparator + 1) === totalText.slice(totalSeparator + 1))
usedText = usedText.slice(0, usedSeparator)
return usedText + " / " + totalText
}
function primaryGpuUsage() {
var adapters = Array.isArray(gpus) ? gpus : []
if (adapters.length === 0) return -1
var usage = Number(adapters[0].usage)
return isFinite(usage) ? usage : -1
}
function primaryGpuUsageText() {
var adapters = Array.isArray(gpus) ? gpus : []
if (gpuSnapshot.sample <= 0) return "…"
if (adapters.length === 0) return "—"
return gpuUsageText(adapters[0])
}
function gpuCardDetail() {
var adapters = Array.isArray(gpus) ? gpus : []
if (adapters.length === 0)
return gpuSnapshot.sample <= 0 ? "Detecting" : "Not detected"
if (adapters.length === 1)
return gpuMemoryLabel(adapters[0]) + " " + gpuMemoryText(adapters[0])
var parts = []
for (var i = 0; i < Math.min(2, adapters.length); i++)
parts.push(compactGpuName(adapters[i]) + " " + gpuUsageAndClockText(adapters[i]))
if (adapters.length > 2) parts.push(adapters.length + " adapters")
return parts.join(" · ")
}
function resetDriveSelection() {
selectedDiskId = "all"
selectedStoragePath = ""
storageSelectionPinned = false
}
function cycleDisk(delta) {
var next = Model.cycleItemId(diskItems, selectedDiskId, "id", delta)
if (next !== "") selectedDiskId = next
}
function cycleStorage(delta) {
var current = activeStorage ? activeStorage.path : selectedStoragePath
var next = Model.cycleItemId(storageItems, current, "path", delta)
if (next === "") return
storageSelectionPinned = true
selectedStoragePath = next
}
function pagerText(items, currentId, key) {
if (!items || items.length <= 1) return ""
var wanted = String(currentId || "")
var index = 0
for (var i = 0; i < items.length; i++) {
if (String(items[i][key]) === wanted) {
index = i
break
}
}
return (index + 1) + "/" + items.length
}
function diskCardLabel() {
if (!canCycleDisks) return "Disk"
return activeDisk && activeDisk.id !== "all" ? String(activeDisk.name || "Disk") : "Disk"
}
function diskPagerText() {
return pagerText(diskItems, activeDisk ? activeDisk.id : selectedDiskId, "id")
}
function diskCardRead() {
return activeDisk ? Number(activeDisk.read || 0) : Number(metrics.diskRead || 0)
}
function diskCardWrite() {
return activeDisk ? Number(activeDisk.write || 0) : Number(metrics.diskWrite || 0)
}
function diskCardHistory() {
return activeDisk && Array.isArray(activeDisk.history) ? activeDisk.history : metrics.diskHistory
}
function storageCardLabel() {
if (!canCycleStorage) return "Storage"
return Model.storageVolumeLabel(activeStorage && activeStorage.path)
}
function storagePagerText() {
return pagerText(storageItems, activeStorage ? activeStorage.path : selectedStoragePath, "path")
}
function storageCardValue() {
if (storageSnapshot.sample <= 0) return "—"
return activeStorage ? Model.formatBytes(activeStorage.used) : "—"
}
function storageCardDetail() {
if (storageSnapshot.sample <= 0) return ""
return activeStorage ? Model.formatBytes(activeStorage.available) + " free" : ""
}
function pressureColor(value) {
return Number(value) >= 90 ? urgent : accent
}
function estimatedPowerText(value) {
var watts = Number(value)
if (!isFinite(watts) || watts < 0) return "—"
if (watts < 0.005) return "~0 W"
if (watts < 1) return "~" + watts.toFixed(2) + " W"
if (watts < 10) return "~" + watts.toFixed(1) + " W"
return "~" + Math.round(watts) + " W"
}
function measuredPackagePowerText(value) {
var watts = Number(value)
if (!isFinite(watts) || watts < 0) return ""
if (watts < 10) return watts.toFixed(2) + " W"
if (watts < 100) return watts.toFixed(1) + " W"
return Math.round(watts) + " W"
}
function sortLabel(key, label) {
if (sortKey !== key) return label
return label + (sortAscending ? " ↑" : " ↓")
}
function shortcutHintText() {
return "e collapse · / search · "
+ (powerEstimatesEnabled ? "c/m/w/p/t/n" : "c/m/p/t/n")
+ " sort · r refresh · s settings · j/k select · click/x close"
+ ((canCycleDisks || canCycleStorage) ? " · d/v cycle drives" : "")
+ " · ? hide"
}
function requestCloseProcess(process, index) {
if (!process) return
if (index >= 0) {
cursorActive = true
selectProcess(index)
}
processActions.request(process)
}
function selectProcessFromPointer(index, item, mouse) {
// Pointer events can arrive at the mouse polling rate. Once this row owns
// the cursor, avoid coordinate mapping and QML property writes until the
// pointer actually reaches a different row.
if (cursorActive && selectedProcessIndex === index) return
if (!pointerGate.moved(item, mouse)) return
cursorActive = true
selectProcess(index)
}
function processNameColumnWidth(availableWidth, spacing) {
var fixedWidth = processPidColumnWidth
+ processMetricColumnWidth * (powerEstimatesEnabled ? 3 : 2)
var gaps = powerEstimatesEnabled ? 4 : 3
if (showProcessUserColumn) {
fixedWidth += processUserColumnWidth
gaps++
}
if (showProcessTimeColumn) {
fixedWidth += processMetricColumnWidth
gaps++
}
return Math.max(0, availableWidth - fixedWidth - spacing * gaps)
}
onSortedProcessesChanged: {
pointerGate.reset()
restoreProcessCursor()
}
onProcessPowerUnavailableChanged: {
if (processPowerUnavailable && sortKey === "power") {
sortKey = "cpu"
sortAscending = false
}
}
onPowerEstimatesEnabledChanged: {
if (!powerEstimatesEnabled && sortKey === "power") {
sortKey = "cpu"
sortAscending = false
}
}
onOpenedChanged: {
if (opened) {
expanded = openExpandedByDefault
settingsOpen = false
settingsControlActive = false
cursorActive = false
selectedProcessIndex = 0
selectedProcessKey = ""
processQuery = ""
hintsVisible = false
resetDriveSelection()
} else {
expanded = false
settingsOpen = false
settingsControlActive = false
hintsVisible = false
processQuery = ""
resetDriveSelection()
}
}
ActivityController {
id: activity
settings: root.settings
active: root.opened
expanded: root.expanded
processPowerEnabled: root.powerEstimatesEnabled
}
ProcessActionController {
id: processActions
active: root.opened
enabled: !root.settingsOpen
onConfirmationRequested: processConfirm.selectedIndex = 0
onFocusRequested: if (root.opened) keyCatcher.forceActiveFocus()
onRefreshRequested: activity.refreshProcessCycle()
}
PointerMoveGate {
id: pointerGate
referenceItem: root.processViewport
}
BarIconButton {
id: button
anchors.fill: parent
bar: root.bar
text: "\uf201"
tooltipText: "Activity"
onPressed: function(buttonCode) { root.toggle() }
}
KeyboardPanel {
id: panel
anchorItem: button
owner: root
bar: root.bar
open: root.opened
focusTarget: keyCatcher
// Keep both layouts attached to the invoking widget. When the wider
// layout reaches a screen edge, KeyboardPanel grows it inward instead of
// moving the card to the center of the display.
centerOnBar: false
contentWidth: panel.fittedContentWidth(root.settingsOpen
? Style.space(600)
: (root.expanded ? Style.space(780) : Style.space(380)))
// Size the card to the loaded page. Keyboard hints and process status
// live in that page, so showing them grows the window instead of
// painting below the border.
contentHeight: panel.fittedContentHeight(
contentLoader.item ? contentLoader.item.implicitHeight : Style.space(320))
PanelKeyCatcher {
id: keyCatcher
anchors.fill: parent
blocked: !!(root.searchField && root.searchField.activeFocus) || root.settingsControlActive
onMoveRequested: function(dx, dy) {
if (processActions.confirmationOpen) {
if (dx !== 0) processConfirm.selectedIndex = processConfirm.selectedIndex === 0 ? 1 : 0
return
}
if (!root.expanded || root.settingsOpen) return
if (dy !== 0) root.moveProcess(dy)
else if (dx !== 0) root.cycleSort(dx)
}
onActivateRequested: {
if (processActions.confirmationOpen) {
if (processConfirm.selectedIndex === 0) processActions.cancel()
else processActions.confirm()
return
}
if (!root.expanded && !root.settingsOpen) root.setExpanded(true)
}
onCloseRequested: {
if (processActions.confirmationOpen) {
processActions.cancel()
return
}
if (root.settingsOpen) root.setSettingsOpen(false)
else if (root.expanded) root.setExpanded(false)
else root.close()
}
onDeleteRequested: {
if (!root.settingsOpen && !processActions.confirmationOpen && root.cursorActive)
processActions.request(root.selectedProcess)
}
onTabRequested: function(direction) {
if (processActions.confirmationOpen) {
processConfirm.selectedIndex = processConfirm.selectedIndex === 0 ? 1 : 0
return
}
root.switchPanel(direction)
}
onTextKey: function(text) {
if (processActions.confirmationOpen) return
var raw = String(text || "")
var key = raw.toLowerCase()
if (key === "s") root.setSettingsOpen(!root.settingsOpen)
else if (key === "?") root.hintsVisible = !root.hintsVisible
else if (root.settingsOpen) return
else if (key === "e") root.setExpanded(!root.expanded)
else if (key === "r") root.refresh()
else if (key === "/" && root.expanded) root.focusSearch()
else if (key === "d" && root.expanded) root.cycleDisk(raw === "D" ? -1 : 1)
else if (key === "v" && root.expanded) root.cycleStorage(raw === "V" ? -1 : 1)
else if (key === "c" && root.expanded) root.setSort("cpu")
else if (key === "m" && root.expanded) root.setSort("memory")
else if (key === "w" && root.expanded) root.setSort("power")
else if (key === "p" && root.expanded) root.setSort("pid")
else if (key === "t" && root.expanded) root.setSort("runtime")
else if (key === "n" && root.expanded) root.setSort("name")
}
Loader {
id: contentLoader
width: parent.width
sourceComponent: root.settingsOpen
? settingsContent
: (root.expanded ? expandedContent : compactContent)
}
ConfirmDialog {
id: processConfirm
anchors.fill: parent
opened: processActions.confirmationOpen
z: 20
message: processActions.pendingAction
? "Do you want to close " + processActions.pendingAction.name + "?"
: ""
confirmText: "Close"
background: Color.popups.background
foreground: root.foreground
selectedText: root.accent
fontFamily: root.bar ? root.bar.fontFamily : Style.font.family
cornerRadius: Style.cornerRadius
onCanceled: processActions.cancel()
onConfirmed: processActions.confirm()
}
}
}
Component {
id: activityIcon
Text {
text: "\uf201"
color: root.foreground
font.family: root.bar ? root.bar.fontFamily : Style.font.family
font.pixelSize: Style.font.display
}
}
Component {
id: compactContent
Column {
width: contentLoader.width
spacing: Style.spacing.panelGap
ActivityHeader {
width: parent.width
expanded: false
}
Row {
width: parent.width
spacing: Style.spacing.md
MetricCard {
width: (parent.width - parent.spacing) / 2
label: root.cpuCardLabel()
value: root.hasSnapshot ? root.percentText(root.metrics.cpu) : "—"
detail: root.cpuCardDetail()
paint: root.corePaintCompact
history: root.metrics.cpuHistory
ceiling: 100
tone: root.pressureColor(root.metrics.cpu)
}
MetricCard {
width: (parent.width - parent.spacing) / 2
label: root.memoryCardLabel()
value: root.hasSnapshot ? root.percentText(root.metrics.memory) : "—"
detail: root.memoryBreakdownText()
badge: root.swapBadgeText()
badgeTone: root.snapshot.memory.swapTotal
? root.pressureColor(root.metrics.swap)
: root.dim
history: root.metrics.memoryHistory
ceiling: 100
tone: root.pressureColor(root.metrics.memory)
}
}
Row {
width: parent.width
spacing: Style.spacing.panelGap
ActivityPair {
width: (parent.width - parent.spacing) / 2
iconText: "\uf019"
label: "Network"
value: Model.formatBytes(root.metrics.download, "/s")
secondary: "\uf093 " + Model.formatBytes(root.metrics.upload, "/s")
}
ActivityPair {
width: (parent.width - parent.spacing) / 2
iconText: "\uf0a0"
label: "Disk"
value: Model.formatBytes(root.metrics.diskRead, "/s")
secondary: "\uf093 " + Model.formatBytes(root.metrics.diskWrite, "/s")
}
}
PanelSeparator {
width: parent.width
foreground: root.foreground
}
Column {
width: parent.width
spacing: Style.spacing.sm
PanelSectionHeader {
text: "Busiest"
foreground: root.foreground
fontFamily: root.bar ? root.bar.fontFamily : Style.font.family
}
Text {
visible: root.topProcesses.length === 0
text: root.processMetricsReady ? "No running processes" : "Sampling process activity…"
color: root.dim
font.family: root.bar ? root.bar.fontFamily : Style.font.family
font.pixelSize: Style.font.body
}
Repeater {
model: root.topProcesses
ProcessRow {
required property var modelData
width: parent.width
processData: modelData
compact: true
}
}
}
Text {
width: parent.width
visible: processActions.status !== "" || root.hintsVisible
text: processActions.status || "e details · s settings · ? hide"
textFormat: Text.PlainText
color: processActions.failed ? root.urgent : root.dim
font.family: root.bar ? root.bar.fontFamily : Style.font.family
font.pixelSize: Style.font.caption
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.WordWrap
}
}
}
Component {
id: settingsContent
Column {
id: settingsPage
width: contentLoader.width
spacing: Style.spacing.panelGap
Keys.priority: Keys.AfterItem
Keys.onPressed: function(event) {
if (event.key === Qt.Key_Escape || String(event.text || "").toLowerCase() === "s") {
root.setSettingsOpen(false)
event.accepted = true
}
}
Component.onCompleted: Qt.callLater(function() { updateSpeedDropdown.forceActiveFocus() })
Component.onDestruction: root.settingsControlActive = false
ActivityHeader {
width: parent.width
expanded: root.expanded
}
DetailSurface {
width: parent.width
height: settingsBody.implicitHeight + Style.spacing.xl * 2
Column {
id: settingsBody
x: Style.spacing.xl
y: Style.spacing.xl
width: Math.max(1, parent.width - Style.spacing.xl * 2)
spacing: Style.spacing.md
PanelSectionHeader {
text: "Settings"
foreground: root.foreground
fontFamily: root.bar ? root.bar.fontFamily : Style.font.family
}
Text {
width: parent.width
text: "Changes apply immediately and are saved with your Omarchy bar layout."
color: root.dim
font.family: root.bar ? root.bar.fontFamily : Style.font.family
font.pixelSize: Style.font.caption
wrapMode: Text.WordWrap
}
Grid {
id: preferenceGrid
width: parent.width
columns: width >= Style.space(500) ? 2 : 1
columnSpacing: Style.spacing.xl
rowSpacing: Style.spacing.md
Dropdown {
id: updateSpeedDropdown
width: (preferenceGrid.width
- preferenceGrid.columnSpacing * (preferenceGrid.columns - 1))
/ preferenceGrid.columns
label: "Update speed"
value: root.samplingSpeedSetting
options: [
{ value: "Efficient", label: "Efficient · 3 s" },
{ value: "Balanced", label: "Balanced · 1.5 s" },
{ value: "Fast", label: "Fast · 0.75 s" }
]
foreground: root.foreground
accent: root.accent
fontFamily: root.bar ? root.bar.fontFamily : Style.font.family
onChanged: function(next) { root.persistSettings({ samplingSpeed: next }) }
}
Dropdown {
width: (preferenceGrid.width
- preferenceGrid.columnSpacing * (preferenceGrid.columns - 1))
/ preferenceGrid.columns
label: "Graph history"
value: String(root.historySamplesSetting)
options: [
{ value: "30", label: "Short · 30 samples" },
{ value: "60", label: "Medium · 60 samples" },
{ value: "120", label: "Long · 120 samples" }
]
foreground: root.foreground
accent: root.accent
fontFamily: root.bar ? root.bar.fontFamily : Style.font.family
onChanged: function(next) { root.persistSettings({ historySamples: Number(next) }) }
}
Dropdown {
width: (preferenceGrid.width
- preferenceGrid.columnSpacing * (preferenceGrid.columns - 1))
/ preferenceGrid.columns
label: "Temperature"
value: root.temperatureUnitSetting
options: ["Celsius", "Fahrenheit"]
foreground: root.foreground
accent: root.accent
fontFamily: root.bar ? root.bar.fontFamily : Style.font.family
onChanged: function(next) { root.persistSettings({ temperatureUnit: next }) }
}
Toggle {
width: (preferenceGrid.width
- preferenceGrid.columnSpacing * (preferenceGrid.columns - 1))
/ preferenceGrid.columns
label: "Open detailed view"
description: "Start with GPU, graphs, and the full process table."
checked: root.openExpandedByDefault
foreground: root.foreground
accent: root.accent
fontFamily: root.bar ? root.bar.fontFamily : Style.font.family
onClicked: root.persistSettings({ openExpanded: !root.openExpandedByDefault })
}
Toggle {
width: (preferenceGrid.width
- preferenceGrid.columnSpacing * (preferenceGrid.columns - 1))
/ preferenceGrid.columns
label: "Show hardware speeds"
description: "Include CPU, RAM, and GPU speeds in headings."
checked: root.showFrequencies
foreground: root.foreground
accent: root.accent
fontFamily: root.bar ? root.bar.fontFamily : Style.font.family
onClicked: root.persistSettings({ showFrequencies: !root.showFrequencies })
}
Toggle {
width: (preferenceGrid.width
- preferenceGrid.columnSpacing * (preferenceGrid.columns - 1))
/ preferenceGrid.columns
label: "Process power estimates"
description: "Measure package energy in detailed view; disable for less work."
checked: root.powerEstimatesEnabled
foreground: root.foreground
accent: root.accent
fontFamily: root.bar ? root.bar.fontFamily : Style.font.family
onClicked: root.persistSettings({ processPowerEnabled: !root.powerEstimatesEnabled })
}
}
}
}
Row {
width: parent.width
spacing: Style.spacing.md
Button {
width: (parent.width - parent.spacing) / 2
text: "Reset defaults"
iconText: "\uf2ea"
foreground: root.dim
accent: root.accent
fontFamily: root.bar ? root.bar.fontFamily : Style.font.family
focusable: true
bordered: true
onClicked: root.resetPreferences()
}
Button {
width: (parent.width - parent.spacing) / 2
text: "Done"
iconText: "\uf00c"
foreground: root.foreground
accent: root.accent
fontFamily: root.bar ? root.bar.fontFamily : Style.font.family
focusable: true
bordered: true
onClicked: root.setSettingsOpen(false)
}
}
}
}
Component {
id: expandedContent
Column {
width: contentLoader.width
spacing: Style.spacing.panelGap
ActivityHeader {
width: parent.width
expanded: true
}
Grid {
id: summaryGrid
width: parent.width
columns: width >= Style.space(640) ? 3 : 2
spacing: Style.spacing.md
MetricCard {
width: (summaryGrid.width - summaryGrid.spacing * (summaryGrid.columns - 1)) / summaryGrid.columns
label: root.cpuCardLabel()
value: root.hasSnapshot ? root.percentText(root.metrics.cpu) : "—"
detail: root.cpuCardDetail()
paint: root.corePaintExpanded
tall: true
history: root.metrics.cpuHistory
ceiling: 100
tone: root.pressureColor(root.metrics.cpu)
}
MetricCard {
width: (summaryGrid.width - summaryGrid.spacing * (summaryGrid.columns - 1)) / summaryGrid.columns
label: root.memoryCardLabel()
value: root.hasSnapshot ? root.percentText(root.metrics.memory) : "—"
detail: root.memoryBreakdownText()
badge: root.swapBadgeText()
badgeTone: root.snapshot.memory.swapTotal
? root.pressureColor(root.metrics.swap)
: root.dim
history: root.metrics.memoryHistory
ceiling: 100
tall: true
tone: root.pressureColor(root.metrics.memory)
}
MetricCard {
width: (summaryGrid.width - summaryGrid.spacing * (summaryGrid.columns - 1)) / summaryGrid.columns
label: root.gpuHeadingText()
value: root.primaryGpuUsageText()
detail: root.gpuCardDetail()
tone: root.primaryGpuUsage() >= 0
? root.pressureColor(root.primaryGpuUsage())
: root.accent
tall: true
}
MetricCard {
width: (summaryGrid.width - summaryGrid.spacing * (summaryGrid.columns - 1)) / summaryGrid.columns
label: "Network"
value: Model.formatBytes(root.metrics.download, "/s")
detail: "\uf093 " + Model.formatBytes(root.metrics.upload, "/s")
history: root.metrics.networkHistory
tone: root.accent
tall: true
}
MetricCard {
width: (summaryGrid.width - summaryGrid.spacing * (summaryGrid.columns - 1)) / summaryGrid.columns
label: root.diskCardLabel()
badge: root.diskPagerText()
value: Model.formatBytes(root.diskCardRead(), "/s")
detail: "\uf093 " + Model.formatBytes(root.diskCardWrite(), "/s")
history: root.diskCardHistory()
tone: root.accent
tall: true
interactive: root.canCycleDisks
tooltipText: root.canCycleDisks ? "Next disk" : ""
onActivated: function(delta) { root.cycleDisk(delta) }
}
MetricCard {
width: (summaryGrid.width - summaryGrid.spacing * (summaryGrid.columns - 1)) / summaryGrid.columns
label: root.storageCardLabel()
badge: root.storagePagerText()
value: root.storageCardValue()
detail: root.storageCardDetail()
bar: root.storageSnapshot.sample > 0 ? root.storageUsedFraction : -1
tone: root.pressureColor(root.storageUsedFraction * 100)
tall: true
interactive: root.canCycleStorage
tooltipText: root.canCycleStorage ? "Next volume" : ""
onActivated: function(delta) { root.cycleStorage(delta) }
}
}
PanelSeparator {
width: parent.width
foreground: root.foreground
}
Row {
width: parent.width
spacing: Style.spacing.sm
PanelSectionHeader {
text: "Processes"
foreground: root.foreground
fontFamily: root.bar ? root.bar.fontFamily : Style.font.family
anchors.verticalCenter: parent.verticalCenter
}
Item {
width: Math.max(0, parent.width - parent.children[0].implicitWidth
- searchBox.width - parent.spacing * 2)
height: 1
}
TextField {
id: searchBox
width: Math.max(Style.space(120), Math.min(Style.space(180), parent.width * 0.24))
placeholderText: "\uf002 Search"
foreground: root.foreground
accent: root.accent
font.pixelSize: Style.font.bodySmall
verticalPadding: Style.spacing.sm
onTextChanged: {
root.processQuery = text
root.selectedProcessIndex = 0
root.selectedProcessKey = ""
root.cursorActive = false
pointerGate.reset()
}
Keys.onPressed: function(event) {
if (event.key === Qt.Key_Escape || event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
root.finishSearch()
event.accepted = true
}
}
Component.onCompleted: {
text = root.processQuery
root.searchField = searchBox
}
Component.onDestruction: if (root.searchField === searchBox) root.searchField = null
}
}
ProcessHeader {
width: parent.width
}
Item {
id: processListHost
width: parent.width
height: Style.space(300)
clip: true
ListView {
id: processList
anchors.fill: parent
model: root.sortedProcesses
spacing: Style.spacing.xxs
clip: true
boundsBehavior: Flickable.StopAtBounds
reuseItems: true
function ensureSelected() {
if (root.selectedProcessIndex >= 0 && root.selectedProcessIndex < count)
positionViewAtIndex(root.selectedProcessIndex, ListView.Contain)
}
Component.onCompleted: root.processViewport = processList
Component.onDestruction: if (root.processViewport === processList) root.processViewport = null
ScrollBar.vertical: ScrollBar {
policy: ScrollBar.AsNeeded
}
delegate: ProcessRow {
required property var modelData
required property int index
width: ListView.view.width
processData: modelData
rowIndex: index
compact: false
selected: root.cursorActive
&& root.selectedProcessKey === Model.processIdentityKey(modelData)
}
}
Text {
anchors.centerIn: parent
visible: processList.count === 0
text: root.processQuery
? "No processes match “" + root.processQuery + "”"
: (root.processMetricsReady ? "No running processes" : "Sampling process activity…")
textFormat: Text.PlainText
color: root.dim
font.family: root.bar ? root.bar.fontFamily : Style.font.family
font.pixelSize: Style.font.body
}
}
Text {
id: expandedHint
width: parent.width
visible: processActions.status !== "" || root.hintsVisible
text: processActions.status || root.shortcutHintText()
textFormat: Text.PlainText
color: processActions.failed ? root.urgent : root.dim
font.family: root.bar ? root.bar.fontFamily : Style.font.family
font.pixelSize: Style.font.caption
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.WordWrap
}
}
}
component ActivityHeader: Row {
property bool expanded: false
spacing: Style.spacing.md
PanelHero {
id: hero
width: Math.max(1, parent.width - headerActions.implicitWidth - parent.spacing)
anchors.verticalCenter: parent.verticalCenter
iconComponent: activityIcon
title: "Activity"
meta: root.hasSnapshot ? Model.formatDuration(root.snapshot.uptime) : "Reading system"
foreground: root.foreground
fontFamily: root.bar ? root.bar.fontFamily : Style.font.family
}
Row {
id: headerActions
anchors.verticalCenter: parent.verticalCenter
spacing: Style.spacing.sm
PanelActionButton {
visible: !root.settingsOpen
iconText: "\uf128"
tooltipText: root.hintsVisible ? "Hide keyboard shortcuts" : "Keyboard shortcuts"
foreground: root.hintsVisible ? root.accent : root.foreground
hoverColor: root.accent
fontFamily: root.bar ? root.bar.fontFamily : Style.font.family
bordered: true
onClicked: root.hintsVisible = !root.hintsVisible
}
PanelActionButton {
id: settingsButton
iconText: "\uf013"
tooltipText: root.settingsOpen ? "Close settings" : "Activity settings"
foreground: root.settingsOpen ? root.accent : root.foreground
hoverColor: root.accent
fontFamily: root.bar ? root.bar.fontFamily : Style.font.family
bordered: true
onClicked: root.setSettingsOpen(!root.settingsOpen)
}
PanelActionButton {
id: expandButton
visible: !root.settingsOpen
iconText: expanded ? "\uf066" : "\uf065"
tooltipText: expanded ? "Collapse details" : "Expand details"
foreground: root.foreground
fontFamily: root.bar ? root.bar.fontFamily : Style.font.family
bordered: true
onClicked: root.setExpanded(!expanded)
}
}
}
component MetricCard: BorderSurface {
id: metricCard
property string label: ""
property string badge: ""
property string value: ""
property string detail: ""
property var history: []
property var paint: ({ width: 0, height: 0, frames: [], cells: [] })
property real ceiling: 0
property real bar: -1
property bool tall: false
property bool interactive: false
property string tooltipText: ""
property color tone: root.accent
property color badgeTone: root.dim
readonly property bool showCores: !!(paint && Array.isArray(paint.cells) && paint.cells.length > 0)
signal activated(int delta)
implicitHeight: Style.space(tall ? 124 : 92)
color: Style.normalFillFor(root.foreground, root.accent)
borderSpec: Border.controlSpec("normal", root.foreground, root.accent)
radius: Style.cornerRadius
Column {
anchors.fill: parent
anchors.margins: Style.spacing.lg
spacing: Style.spacing.xxs
Item {
width: parent.width
implicitHeight: Math.max(cardLabel.implicitHeight, cardBadge.implicitHeight)
PanelSectionHeader {
id: cardLabel
anchors.left: parent.left
anchors.right: cardBadge.visible ? cardBadge.left : parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.rightMargin: cardBadge.visible ? Style.spacing.sm : 0
text: metricCard.label
foreground: root.foreground
fontFamily: root.bar ? root.bar.fontFamily : Style.font.family
elide: Text.ElideRight
}
Text {
id: cardBadge
visible: metricCard.badge !== ""
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
text: metricCard.badge
color: metricCard.badgeTone
font.family: root.bar ? root.bar.fontFamily : Style.font.family
font.pixelSize: Style.font.caption
font.bold: true
}
}
Item {
width: parent.width
implicitHeight: Math.max(valueBlock.implicitHeight, coreDie.implicitHeight)
Column {
id: valueBlock
anchors.left: parent.left
anchors.right: coreDie.visible ? coreDie.left : parent.right
anchors.rightMargin: coreDie.visible ? Style.spacing.sm : 0
anchors.verticalCenter: parent.verticalCenter
spacing: Style.spacing.xxs
Text {
width: parent.width
text: value
color: tone
font.family: root.bar ? root.bar.fontFamily : Style.font.family
font.pixelSize: Style.font.title
font.bold: true
elide: Text.ElideRight
}
Text {
width: parent.width
text: detail
color: root.dim
font.family: root.bar ? root.bar.fontFamily : Style.font.family
font.pixelSize: Style.font.caption
elide: Text.ElideRight
}
}
CoreHeatGrid {
id: coreDie
visible: metricCard.showCores
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
paint: metricCard.paint
}
}
}
Sparkline {
visible: metricCard.bar < 0 && Array.isArray(history) && history.length > 0
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.leftMargin: Style.spacing.lg
anchors.rightMargin: Style.spacing.lg
anchors.bottomMargin: Style.spacing.sm
height: Style.space(20)
values: history
ceiling: parent.ceiling
lineColor: tone
fillColor: Util.alpha(tone, 0.10)
}
Rectangle {
visible: metricCard.bar >= 0
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.leftMargin: Style.spacing.lg
anchors.rightMargin: Style.spacing.lg
anchors.bottomMargin: Style.spacing.sm
height: Style.space(4)
radius: height / 2
color: Util.alpha(root.foreground, 0.12)
Rectangle {
width: parent.width * Math.max(0, Math.min(1, metricCard.bar))
height: parent.height
radius: parent.radius
color: metricCard.tone
}
}
MouseArea {
id: cardClick
anchors.fill: parent
enabled: metricCard.interactive
hoverEnabled: metricCard.interactive
cursorShape: metricCard.interactive ? Qt.PointingHandCursor : Qt.ArrowCursor
acceptedButtons: Qt.LeftButton
onClicked: function(mouse) {
metricCard.activated((mouse.modifiers & Qt.ShiftModifier) ? -1 : 1)
}
PanelToolTip {
visible: metricCard.interactive && metricCard.tooltipText !== "" && cardClick.containsMouse
text: metricCard.tooltipText
fontFamily: root.bar ? root.bar.fontFamily : Style.font.family
}
}
}
component ActivityPair: Row {
property string iconText: ""
property string label: ""
property string value: ""
property string secondary: ""
spacing: Style.spacing.md
Text {
id: icon
anchors.verticalCenter: parent.verticalCenter
text: iconText
color: root.accent
font.family: root.bar ? root.bar.fontFamily : Style.font.family
font.pixelSize: Style.font.title
}
Column {
id: labels
width: Math.max(1, parent.width - icon.implicitWidth - parent.spacing)
spacing: Style.spacing.xxs
Text {
text: label
color: root.dim
font.family: root.bar ? root.bar.fontFamily : Style.font.family
font.pixelSize: Style.font.caption
font.bold: true
}
Text {
width: parent.width
text: value
color: root.foreground
font.family: root.bar ? root.bar.fontFamily : Style.font.family
font.pixelSize: Style.font.body
font.bold: true
elide: Text.ElideRight
}
Text {
width: parent.width
text: secondary
color: root.dim
font.family: root.bar ? root.bar.fontFamily : Style.font.family
font.pixelSize: Style.font.caption
elide: Text.ElideRight
}
}
}
component DetailSurface: BorderSurface {
color: Style.normalFillFor(root.foreground, root.accent)
borderSpec: Border.controlSpec("normal", root.foreground, root.accent)
radius: Style.cornerRadius
}
component CoreHeatGrid: Item {
id: heat
property var paint: ({ width: 0, height: 0, frames: [], cells: [] })
readonly property var frames: paint && Array.isArray(paint.frames) ? paint.frames : []
readonly property var cells: paint && Array.isArray(paint.cells) ? paint.cells : []
implicitWidth: Math.max(0, Number(paint && paint.width || 0))
implicitHeight: Math.max(0, Number(paint && paint.height || 0))
function cellLabel(cell) {
var kind = String(cell && cell.kind || "same")
var index = String(cell && cell.name || "").replace(/^cpu/i, "")
var usage = Math.round(Number(cell && cell.usage || 0))
if (kind === "performance") return "P-core " + index + " · " + usage + "%"
if (kind === "efficiency") return "E-core " + index + " · " + usage + "%"
if (kind === "lowpower") return "LP-E " + index + " · " + usage + "%"
return "Core " + index + " · " + usage + "%"
}
Repeater {
model: heat.frames.length
Rectangle {
required property int index
readonly property var frame: heat.frames[index] || ({})
x: Number(frame.x || 0)
y: Number(frame.y || 0)
width: Number(frame.w || 0)
height: Number(frame.h || 0)
radius: 2
color: "transparent"
border.width: 1
border.color: Util.alpha(root.foreground, 0.28)
}
}
Repeater {
model: heat.cells.length
CoreDieCell {
required property int index
readonly property var cell: heat.cells[index] || ({})
x: Number(cell.x || 0)
y: Number(cell.y || 0)
size: Number(cell.size || 0)
usage: Number(cell.usage || 0)
tooltipText: heat.cellLabel(cell)
}
}
}
component CoreDieCell: Rectangle {
property int size: Style.space(7)
property real usage: 0
property string tooltipText: ""
width: size
height: size
radius: 1
color: "transparent"
border.width: 1
border.color: Util.alpha(root.foreground, 0.22)
Rectangle {
anchors.fill: parent
anchors.margins: 1
radius: 0
color: root.pressureColor(parent.usage)
opacity: parent.usage < 8 ? 0 : 0.28 + 0.72 * Math.pow(parent.usage / 100, 1.35)
}
MouseArea {
id: cellHover
anchors.fill: parent
hoverEnabled: true
PanelToolTip {
visible: cellHover.containsMouse && cellHover.parent.tooltipText !== ""
text: cellHover.parent.tooltipText
fontFamily: root.bar ? root.bar.fontFamily : Style.font.family
}
}
}
component ProcessHeader: Item {
implicitHeight: headerRow.implicitHeight
Row {
id: headerRow
anchors.left: parent.left
anchors.right: parent.right
anchors.leftMargin: Style.spacing.md
anchors.rightMargin: Style.spacing.md
anchors.verticalCenter: parent.verticalCenter
spacing: Style.spacing.md
ProcessSortCell {
width: root.processNameColumnWidth(parent.width, parent.spacing)
sortKey: "name"
label: "Name"
}
ProcessSortCell {
width: root.processPidColumnWidth
sortKey: "pid"
label: "PID"
alignment: Text.AlignRight
}
ProcessCell {
visible: root.showProcessUserColumn
width: root.processUserColumnWidth
text: "User"
dimmed: true
}
ProcessSortCell {
width: root.processMetricColumnWidth
sortKey: "cpu"
label: "CPU"
alignment: Text.AlignRight
}
ProcessSortCell {
width: root.processMetricColumnWidth
sortKey: "memory"
label: "RAM"
alignment: Text.AlignRight
}
ProcessSortCell {
visible: root.powerEstimatesEnabled
width: root.processMetricColumnWidth
sortKey: "power"
label: "~W"
alignment: Text.AlignRight
tooltipText: root.processPowerTooltip
}
ProcessSortCell {
visible: root.showProcessTimeColumn
width: root.processMetricColumnWidth
sortKey: "runtime"
label: "Time"
alignment: Text.AlignRight
}
}
}
component ProcessSortCell: Item {
id: sortCell
property string sortKey: ""
property string label: ""
property string tooltipText: ""
property int alignment: Text.AlignLeft
implicitHeight: labelText.implicitHeight
ProcessCell {
id: labelText
width: parent.width
text: root.sortLabel(sortCell.sortKey, sortCell.label)
dimmed: root.sortKey !== sortCell.sortKey
emphasized: root.sortKey === sortCell.sortKey
alignment: sortCell.alignment
}
MouseArea {
id: sortHover
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: root.setSort(sortCell.sortKey)
PanelToolTip {
visible: sortCell.tooltipText !== "" && sortHover.containsMouse
text: sortCell.tooltipText
fontFamily: root.bar ? root.bar.fontFamily : Style.font.family
}
}
}
component ProcessRow: CursorSurface {
property var processData: ({})
property int rowIndex: -1
property bool compact: false
property bool selected: false
hasCursor: selected
foreground: root.foreground
accent: root.accent
implicitHeight: compact ? Style.space(34) : Style.space(28)
Row {
anchors.fill: parent
anchors.leftMargin: compact ? Style.spacing.sm : Style.spacing.md
anchors.rightMargin: compact ? Style.spacing.sm : Style.spacing.md
spacing: Style.spacing.md
Column {
width: compact
? Math.max(1, parent.width - cpuValue.width - memoryValue.width - parent.spacing * 2)
: root.processNameColumnWidth(parent.width, parent.spacing)
anchors.verticalCenter: parent.verticalCenter
spacing: 0
Text {
width: parent.width
text: String(processData.name || "unknown")
textFormat: Text.PlainText
color: root.foreground
font.family: root.bar ? root.bar.fontFamily : Style.font.family
font.pixelSize: compact ? Style.font.body : Style.font.bodySmall
font.bold: compact
elide: Text.ElideRight
}
Text {
visible: compact
width: parent.width
text: String(processData.user || "") + " · " + String(processData.pid || "")
textFormat: Text.PlainText
color: root.dim
font.family: root.bar ? root.bar.fontFamily : Style.font.family
font.pixelSize: Style.font.caption
elide: Text.ElideRight
}
}
ProcessCell {
visible: !compact
width: root.processPidColumnWidth
text: String(processData.pid || "")
alignment: Text.AlignRight
}
ProcessCell {
visible: !compact && root.showProcessUserColumn
width: root.processUserColumnWidth
text: String(processData.user || "")
}
ProcessCell {
id: cpuValue
width: compact ? Style.space(46) : root.processMetricColumnWidth
text: Number(processData.cpu || 0).toFixed(1) + "%"
alignment: Text.AlignRight
emphasized: Number(processData.cpu || 0) >= 25
}
ProcessCell {
id: memoryValue
width: compact ? Style.space(46) : root.processMetricColumnWidth
text: Number(processData.memory || 0).toFixed(1) + "%"
alignment: Text.AlignRight
}
ProcessCell {
visible: !compact && root.powerEstimatesEnabled
width: root.processMetricColumnWidth
text: root.estimatedPowerText(processData.power)
alignment: Text.AlignRight
}
ProcessCell {
visible: !compact && root.showProcessTimeColumn
width: root.processMetricColumnWidth
text: Model.formatDuration(processData.elapsed)
alignment: Text.AlignRight
}
}
MouseArea {
anchors.fill: parent
hoverEnabled: !compact
cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton
onPositionChanged: function(mouse) {
if (!compact) root.selectProcessFromPointer(parent.rowIndex, parent, mouse)
}
onClicked: root.requestCloseProcess(parent.processData, compact ? -1 : parent.rowIndex)
}
}
component ProcessCell: Text {
property bool dimmed: false
property bool emphasized: false
property int alignment: Text.AlignLeft
color: emphasized ? root.accent : (dimmed ? root.dim : root.foreground)
font.family: root.bar ? root.bar.fontFamily : Style.font.family
font.pixelSize: dimmed ? Style.font.caption : Style.font.bodySmall
font.bold: emphasized || dimmed
textFormat: Text.PlainText
elide: Text.ElideRight
horizontalAlignment: alignment
verticalAlignment: Text.AlignVCenter
anchors.verticalCenter: parent ? parent.verticalCenter : undefined
}
}