mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2025-12-08 06:25:37 -05:00
When there are several batteries, one of them is fully charged, and the other is discharging, this leads to the incorrect display of the overall status as “Charging”
229 lines
8.2 KiB
QML
229 lines
8.2 KiB
QML
pragma Singleton
|
|
|
|
pragma ComponentBehavior: Bound
|
|
|
|
import QtQuick
|
|
import Quickshell
|
|
import Quickshell.Io
|
|
import Quickshell.Services.UPower
|
|
import qs.Common
|
|
|
|
Singleton {
|
|
id: root
|
|
|
|
property bool suppressSound: true
|
|
property bool previousPluggedState: false
|
|
|
|
Timer {
|
|
id: startupTimer
|
|
interval: 500
|
|
repeat: false
|
|
running: true
|
|
onTriggered: root.suppressSound = false
|
|
}
|
|
|
|
readonly property string preferredBatteryOverride: Quickshell.env("DMS_PREFERRED_BATTERY")
|
|
|
|
// List of laptop batteries
|
|
readonly property var batteries: UPower.devices.values.filter(dev => dev.isLaptopBattery)
|
|
|
|
readonly property bool usePreferred: preferredBatteryOverride && preferredBatteryOverride.length > 0
|
|
|
|
// Main battery (for backward compatibility)
|
|
readonly property UPowerDevice device: {
|
|
var preferredDev
|
|
if (usePreferred) {
|
|
preferredDev = batteries.find(dev => dev.nativePath.toLowerCase().includes(preferredBatteryOverride.toLowerCase()))
|
|
}
|
|
return preferredDev || batteries[0] || null
|
|
}
|
|
// Whether at least one battery is available
|
|
readonly property bool batteryAvailable: batteries.length > 0
|
|
// Aggregated charge level (percentage)
|
|
readonly property real batteryLevel: {
|
|
if (!batteryAvailable) return 0
|
|
if (batteryCapacity === 0) {
|
|
if (usePreferred && device && device.ready) return Math.round(device.percentage)
|
|
const validBatteries = batteries.filter(b => b.ready && b.percentage >= 0)
|
|
if (validBatteries.length === 0) return 0
|
|
const avgPercentage = validBatteries.reduce((sum, b) => sum + b.percentage, 0) / validBatteries.length
|
|
return Math.round(avgPercentage)
|
|
}
|
|
return Math.round((batteryEnergy * 100) / batteryCapacity)
|
|
}
|
|
readonly property bool isCharging: batteryAvailable && batteries.some(b => b.state === UPowerDeviceState.Charging)
|
|
|
|
// Is the system plugged in (none of the batteries are discharging or empty)
|
|
readonly property bool isPluggedIn: batteryAvailable && batteries.every(b => b.state !== UPowerDeviceState.Discharging)
|
|
readonly property bool isLowBattery: batteryAvailable && batteryLevel <= 20
|
|
|
|
onIsPluggedInChanged: {
|
|
if (suppressSound || !batteryAvailable) {
|
|
previousPluggedState = isPluggedIn
|
|
return
|
|
}
|
|
|
|
if (SettingsData.soundsEnabled && SettingsData.soundPluggedIn) {
|
|
if (isPluggedIn && !previousPluggedState) {
|
|
AudioService.playPowerPlugSound()
|
|
} else if (!isPluggedIn && previousPluggedState) {
|
|
AudioService.playPowerUnplugSound()
|
|
}
|
|
}
|
|
|
|
previousPluggedState = isPluggedIn
|
|
}
|
|
|
|
// Aggregated charge/discharge rate
|
|
readonly property real changeRate: {
|
|
if (!batteryAvailable) return 0
|
|
if (usePreferred && device && device.ready) return device.changeRate
|
|
return batteries.length > 0 ? batteries.reduce((sum, b) => sum + b.changeRate, 0) : 0
|
|
}
|
|
|
|
// Aggregated battery health
|
|
readonly property string batteryHealth: {
|
|
if (!batteryAvailable) return "N/A"
|
|
|
|
// If a preferred battery is selected and ready
|
|
if (usePreferred && device && device.ready && device.healthSupported) return `${Math.round(device.healthPercentage)}%`
|
|
|
|
// Otherwise, calculate the average health of all laptop batteries
|
|
const validBatteries = batteries.filter(b => b.healthSupported && b.healthPercentage > 0)
|
|
if (validBatteries.length === 0) return "N/A"
|
|
|
|
const avgHealth = validBatteries.reduce((sum, b) => sum + b.healthPercentage, 0) / validBatteries.length
|
|
return `${Math.round(avgHealth)}%`
|
|
}
|
|
|
|
readonly property real batteryEnergy: {
|
|
if (!batteryAvailable) return 0
|
|
if (usePreferred && device && device.ready) return device.energy
|
|
return batteries.length > 0 ? batteries.reduce((sum, b) => sum + b.energy, 0) : 0
|
|
}
|
|
|
|
// Total battery capacity (Wh)
|
|
readonly property real batteryCapacity: {
|
|
if (!batteryAvailable) return 0
|
|
if (usePreferred && device && device.ready) return device.energyCapacity
|
|
return batteries.length > 0 ? batteries.reduce((sum, b) => sum + b.energyCapacity, 0) : 0
|
|
}
|
|
|
|
// Aggregated battery status
|
|
readonly property string batteryStatus: {
|
|
if (!batteryAvailable) {
|
|
return "No Battery"
|
|
}
|
|
|
|
if (isCharging && !batteries.some(b => b.changeRate > 0)) return "Plugged In"
|
|
|
|
const states = batteries.map(b => b.state)
|
|
if (states.every(s => s === states[0])) return UPowerDeviceState.toString(states[0])
|
|
|
|
return isCharging ? "Charging" : (isPluggedIn ? "Plugged In" : "Discharging")
|
|
}
|
|
|
|
readonly property bool suggestPowerSaver: batteryAvailable && isLowBattery && UPower.onBattery && (typeof PowerProfiles !== "undefined" && PowerProfiles.profile !== PowerProfile.PowerSaver)
|
|
|
|
readonly property var bluetoothDevices: {
|
|
const btDevices = []
|
|
const bluetoothTypes = [UPowerDeviceType.BluetoothGeneric, UPowerDeviceType.Headphones, UPowerDeviceType.Headset, UPowerDeviceType.Keyboard, UPowerDeviceType.Mouse, UPowerDeviceType.Speakers]
|
|
|
|
for (var i = 0; i < UPower.devices.count; i++) {
|
|
const dev = UPower.devices.get(i)
|
|
if (dev && dev.ready && bluetoothTypes.includes(dev.type)) {
|
|
btDevices.push({
|
|
"name": dev.model || UPowerDeviceType.toString(dev.type),
|
|
"percentage": Math.round(dev.percentage),
|
|
"type": dev.type
|
|
})
|
|
}
|
|
}
|
|
return btDevices
|
|
}
|
|
|
|
// Format time remaining for charge/discharge
|
|
function formatTimeRemaining() {
|
|
if (!batteryAvailable) {
|
|
return "Unknown"
|
|
}
|
|
|
|
let totalTime = 0
|
|
totalTime = (isCharging) ? ((batteryCapacity - batteryEnergy) / changeRate) : (batteryEnergy / changeRate)
|
|
const avgTime = Math.abs(totalTime * 3600)
|
|
if (!avgTime || avgTime <= 0 || avgTime > 86400) return "Unknown"
|
|
|
|
const hours = Math.floor(avgTime / 3600)
|
|
const minutes = Math.floor((avgTime % 3600) / 60)
|
|
return hours > 0 ? `${hours}h ${minutes}m` : `${minutes}m`
|
|
}
|
|
|
|
function getBatteryIcon() {
|
|
if (!batteryAvailable) {
|
|
return "power"
|
|
}
|
|
|
|
if (isCharging) {
|
|
if (batteryLevel >= 90) {
|
|
return "battery_charging_full"
|
|
}
|
|
if (batteryLevel >= 80) {
|
|
return "battery_charging_90"
|
|
}
|
|
if (batteryLevel >= 60) {
|
|
return "battery_charging_80"
|
|
}
|
|
if (batteryLevel >= 50) {
|
|
return "battery_charging_60"
|
|
}
|
|
if (batteryLevel >= 30) {
|
|
return "battery_charging_50"
|
|
}
|
|
if (batteryLevel >= 20) {
|
|
return "battery_charging_30"
|
|
}
|
|
return "battery_charging_20"
|
|
}
|
|
if (isPluggedIn) {
|
|
if (batteryLevel >= 90) {
|
|
return "battery_charging_full"
|
|
}
|
|
if (batteryLevel >= 80) {
|
|
return "battery_charging_90"
|
|
}
|
|
if (batteryLevel >= 60) {
|
|
return "battery_charging_80"
|
|
}
|
|
if (batteryLevel >= 50) {
|
|
return "battery_charging_60"
|
|
}
|
|
if (batteryLevel >= 30) {
|
|
return "battery_charging_50"
|
|
}
|
|
if (batteryLevel >= 20) {
|
|
return "battery_charging_30"
|
|
}
|
|
return "battery_charging_20"
|
|
}
|
|
if (batteryLevel >= 95) {
|
|
return "battery_full"
|
|
}
|
|
if (batteryLevel >= 85) {
|
|
return "battery_6_bar"
|
|
}
|
|
if (batteryLevel >= 70) {
|
|
return "battery_5_bar"
|
|
}
|
|
if (batteryLevel >= 55) {
|
|
return "battery_4_bar"
|
|
}
|
|
if (batteryLevel >= 40) {
|
|
return "battery_3_bar"
|
|
}
|
|
if (batteryLevel >= 25) {
|
|
return "battery_2_bar"
|
|
}
|
|
return "battery_1_bar"
|
|
}
|
|
}
|