Files
arthur-os/shell/plugins/bar/widgets/powerPanel.qml
T

215 lines
7.4 KiB
QML

import QtQuick
import Quickshell
import Quickshell.Io
import Quickshell.Services.UPower
import qs.Ui
Item {
id: root
property QtObject bar: null
property string moduleName: "powerPanel"
property var settings: ({})
property bool popupOpen: false
property var batteryInfo: ({})
property var systemInfo: ({})
property var profiles: []
property string activeProfile: ""
function closePopout() { popupOpen = false }
function batteryIcon() {
var device = UPower.displayDevice
if (!device || !device.isPresent) return ""
var chargingIcons = ["󰢜", "󰂆", "󰂇", "󰂈", "󰢝", "󰂉", "󰢞", "󰂊", "󰂋", "󰂅"]
var defaultIcons = ["󰁺", "󰁻", "󰁼", "󰁽", "󰁾", "󰁿", "󰂀", "󰂁", "󰂂", "󰁹"]
var index = Math.max(0, Math.min(9, Math.floor(device.percentage * 10)))
if (device.state === UPowerDeviceState.FullyCharged) return "󰂅"
if (!UPower.onBattery && device.state !== UPowerDeviceState.Charging) return ""
if (device.state === UPowerDeviceState.Charging) return chargingIcons[index]
return defaultIcons[index]
}
function modeLabel() { return UPower.onBattery ? "Battery" : "AC" }
function refresh() {
if (!batteryProc.running) batteryProc.running = true
if (!profilesProc.running) profilesProc.running = true
if (!systemProc.running) systemProc.running = true
}
function updateKeyValue(raw, targetName) {
var next = {}
var lines = String(raw || "").split("\n")
for (var i = 0; i < lines.length; i++) {
var idx = lines[i].indexOf("\t")
if (idx <= 0) continue
next[lines[i].substring(0, idx)] = lines[i].substring(idx + 1).trim()
}
if (targetName === "battery") batteryInfo = next
else systemInfo = next
}
function updateProfiles(raw) {
var lines = String(raw || "").split("\n")
var list = []
var active = ""
for (var i = 0; i < lines.length; i++) {
var line = lines[i].trim()
if (!line) continue
var parts = line.split("\t")
list.push(parts[0])
if (parts[1] === "1") active = parts[0]
}
profiles = list
activeProfile = active
}
function setProfile(profile) {
if (!profile || actionProc.running) return
actionProc.command = ["powerprofilesctl", "set", profile]
actionProc.running = true
}
Component.onCompleted: refresh()
implicitWidth: button.implicitWidth
implicitHeight: button.implicitHeight
IpcHandler {
target: "powerPanel"
function toggle(): void { root.popupOpen = !root.popupOpen }
function show(): void { root.popupOpen = true }
function hide(): void { root.closePopout() }
}
Process {
id: batteryProc
command: ["bash", "-lc", `
bat=$(upower -e 2>/dev/null | grep BAT | head -n1)
[[ -z $bat ]] && exit 0
info=$(upower -i "$bat")
printf 'percentage\t%s\n' "$(awk '/percentage/ { print int($2) "%"; exit }' <<<"$info")"
printf 'state\t%s\n' "$(awk '/state/ { print $2; exit }' <<<"$info")"
printf 'rate\t%s\n' "$(awk '/energy-rate/ { v=sprintf("%.1f", $2); sub(/\.0$/, "", v); print v "W"; exit }' <<<"$info")"
printf 'size\t%s\n' "$(awk '/energy-full:/ { printf "%dWh", $2; exit }' <<<"$info")"
printf 'time\t%s\n' "$($OMARCHY_PATH/bin/omarchy-battery-remaining-time 2>/dev/null)"
`]
stdout: StdioCollector { waitForEnd: true; onStreamFinished: root.updateKeyValue(text, "battery") }
}
Process {
id: profilesProc
command: ["bash", "-lc", "powerprofilesctl list 2>/dev/null | awk '/^\\s*[* ]\\s*[a-zA-Z0-9-]+:$/ { active=($1==\"*\"); gsub(/^[*[:space:]]+|:$/,\"\"); print $0 \"\\t\" (active ? 1 : 0) }'"]
stdout: StdioCollector { waitForEnd: true; onStreamFinished: root.updateProfiles(text) }
}
Process {
id: systemProc
command: ["bash", "-lc", "cpu=$(top -bn1 | awk '/^%?Cpu/ { gsub(/,/, \"\"); for (i=1; i<=NF; i++) if ($(i+1) == \"id\") { printf \"%.0f%%\", 100 - $i; exit } }'); awk -v cpu=\"$cpu\" '/^MemTotal:/ { total=$2 } /^MemAvailable:/ { avail=$2 } END { used=total-avail; printf \"cpu\\t%s\\n\", cpu; printf \"memory\\t%.1fGB / %.0fGB\\n\", used/1024/1024, total/1024/1024 }' /proc/meminfo"]
stdout: StdioCollector { waitForEnd: true; onStreamFinished: root.updateKeyValue(text, "system") }
}
Process {
id: actionProc
onExited: root.refresh()
}
Timer { interval: 5000; running: true; repeat: true; triggeredOnStart: true; onTriggered: root.refresh() }
WidgetButton {
id: button
anchors.fill: parent
bar: root.bar
text: root.batteryIcon()
horizontalMargin: 8.5
active: UPower.displayDevice && UPower.displayDevice.percentage <= 0.2 && UPower.onBattery
tooltipText: ""
onPressed: function(b) { root.popupOpen = !root.popupOpen; if (root.popupOpen) root.refresh() }
}
KeyboardPanel {
id: panel
anchorItem: button
owner: root
bar: root.bar
open: root.popupOpen
contentWidth: 340
contentHeight: column.implicitHeight + 28
Column {
id: column
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
spacing: 12
Row {
spacing: 10
Text { text: root.batteryIcon(); color: root.bar.foreground; font.family: root.bar.fontFamily; font.pixelSize: 22 }
Text { text: root.modeLabel(); color: root.bar.foreground; font.family: root.bar.fontFamily; font.pixelSize: 13; font.bold: true }
}
Grid {
visible: root.batteryInfo.percentage !== undefined
width: parent.width
columns: 2
columnSpacing: 14
rowSpacing: 4
InfoLabel { text: "Percentage" } InfoValue { text: root.batteryInfo.percentage || "" }
InfoLabel { text: UPower.onBattery ? "Time left" : "Time to full" } InfoValue { text: root.batteryInfo.time || "—" }
InfoLabel { text: "Battery size" } InfoValue { text: root.batteryInfo.size || "" }
InfoLabel { text: UPower.onBattery ? "Draw" : "Charge rate" } InfoValue { text: root.batteryInfo.rate || "" }
}
PanelSeparator { foreground: root.bar.foreground }
PanelSectionHeader { text: "Power profile"; foreground: root.bar.foreground; fontFamily: root.bar.fontFamily }
Row {
width: parent.width
spacing: 6
Repeater {
model: root.profiles
CursorPill {
required property var modelData
text: String(modelData).charAt(0).toUpperCase() + String(modelData).slice(1)
foreground: root.bar.foreground
tooltipBackground: root.bar.background
tooltipForeground: root.bar.foreground
fontFamily: root.bar.fontFamily
horizontalPadding: 10
verticalPadding: 6
active: root.activeProfile === modelData
onClicked: root.setProfile(modelData)
}
}
}
PanelSeparator { foreground: root.bar.foreground }
PanelSectionHeader { text: "System"; foreground: root.bar.foreground; fontFamily: root.bar.fontFamily }
Grid {
width: parent.width
columns: 2
columnSpacing: 14
rowSpacing: 4
InfoLabel { text: "CPU" } InfoValue { text: root.systemInfo.cpu || "—" }
InfoLabel { text: "Memory" } InfoValue { text: root.systemInfo.memory || "—" }
}
}
}
component InfoLabel: Text {
color: Qt.darker(root.bar.foreground, 1.4)
font.family: root.bar.fontFamily
font.pixelSize: 11
}
component InfoValue: Text {
color: root.bar.foreground
font.family: root.bar.fontFamily
font.pixelSize: 11
}
}