1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2025-12-05 21:15:38 -05:00
Files
DankMaterialShell/quickshell/Services/BatteryService.qml

253 lines
8.6 KiB
QML

pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
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 * 100);
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 * 100);
}
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();
}
}
const profileValue = BatteryService.isPluggedIn
? SettingsData.acProfileName
: SettingsData.batteryProfileName;
if (profileValue !== "") {
const targetProfile = parseInt(profileValue);
if (!isNaN(targetProfile) && PowerProfiles.profile !== targetProfile) {
PowerProfiles.profile = targetProfile;
}
}
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: false
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 * 100),
"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";
}
}