diff options
Diffstat (limited to 'raveos-hyprland-theme/theme-data/dms/Services/BatteryService.qml')
| -rw-r--r-- | raveos-hyprland-theme/theme-data/dms/Services/BatteryService.qml | 271 |
1 files changed, 271 insertions, 0 deletions
diff --git a/raveos-hyprland-theme/theme-data/dms/Services/BatteryService.qml b/raveos-hyprland-theme/theme-data/dms/Services/BatteryService.qml new file mode 100644 index 0000000..e7cea56 --- /dev/null +++ b/raveos-hyprland-theme/theme-data/dms/Services/BatteryService.qml @@ -0,0 +1,271 @@ +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 + + readonly property var scale: 100 / SettingsData.batteryChargeLimit + + 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 * scale); + 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 * scale); + } + return Math.round((batteryEnergy * 100) / batteryCapacity * scale); + } + readonly property bool isCharging: batteryAvailable && batteries.some(b => b.state === UPowerDeviceState.Charging) + + // Is the system plugged in (Is not running on battery) + readonly property bool isPluggedIn: !UPower.onBattery + 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; + } + + function translateBatteryState(state) { + switch (state) { + case UPowerDeviceState.Charging: + return I18n.tr("Charging", "battery status"); + case UPowerDeviceState.Discharging: + return I18n.tr("Discharging", "battery status"); + case UPowerDeviceState.Empty: + return I18n.tr("Empty", "battery status"); + case UPowerDeviceState.FullyCharged: + return I18n.tr("Fully Charged", "battery status"); + case UPowerDeviceState.PendingCharge: + return I18n.tr("Pending Charge", "battery status"); + case UPowerDeviceState.PendingDischarge: + return I18n.tr("Pending Discharge", "battery status"); + default: + return I18n.tr("Unknown", "battery status"); + } + } + + // Aggregated battery status + readonly property string batteryStatus: { + if (!batteryAvailable) { + return I18n.tr("No Battery", "battery status"); + } + + if (isCharging && !batteries.some(b => b.changeRate > 0)) + return I18n.tr("Plugged In", "battery status"); + + const states = batteries.map(b => b.state); + if (states.every(s => s === states[0])) + return translateBatteryState(states[0]); + + return isCharging ? I18n.tr("Charging", "battery status") : (isPluggedIn ? I18n.tr("Plugged In", "battery status") : I18n.tr("Discharging", "battery status")); + } + + 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"; + } +} |