From eb5950f8c81ce2d9e0608545f98a9b56ce54a34a Mon Sep 17 00:00:00 2001 From: purian23 Date: Tue, 15 Jul 2025 18:42:56 -0400 Subject: [PATCH] Fixed battery status on laptops. Updated power profiles. --- Services/BatteryService.qml | 234 +++++++++++++++++++++++------------- 1 file changed, 148 insertions(+), 86 deletions(-) diff --git a/Services/BatteryService.qml b/Services/BatteryService.qml index fca00943..b371d91d 100644 --- a/Services/BatteryService.qml +++ b/Services/BatteryService.qml @@ -1,120 +1,182 @@ import QtQuick import Quickshell import Quickshell.Services.UPower +import Quickshell.Io pragma Singleton pragma ComponentBehavior: Bound Singleton { id: root - // Debug mode for testing on desktop systems without batteries - property bool debugMode: false // Set to true to enable fake battery for testing + // Debug mode for testing (disabled for now) + property bool debugMode: false - // Debug fake battery data - property int debugBatteryLevel: 65 - property string debugBatteryStatus: "Discharging" - property int debugTimeRemaining: 7200 // 2 hours in seconds - property bool debugIsCharging: false - property int debugBatteryHealth: 88 - property string debugBatteryTechnology: "Li-ion" - property int debugBatteryCapacity: 45000 // 45 Wh in mWh + // Battery properties - using shell command method (native UPower API commented out due to issues) + property bool batteryAvailable: systemBatteryPercentage > 0 + property int batteryLevel: systemBatteryPercentage + property string batteryStatus: { + return systemBatteryState === "charging" ? "Charging" : + systemBatteryState === "discharging" ? "Discharging" : + systemBatteryState === "fully-charged" ? "Full" : + systemBatteryState === "empty" ? "Empty" : "Unknown" + } + property int timeRemaining: 0 // Not implemented for shell fallback + property bool isCharging: systemBatteryState === "charging" + property bool isLowBattery: systemBatteryPercentage <= 20 - property bool batteryAvailable: debugMode || (battery.ready && battery.isLaptopBattery) - property int batteryLevel: debugMode ? debugBatteryLevel : Math.round(battery.percentage) - property string batteryStatus: debugMode ? debugBatteryStatus : UPowerDeviceState.toString(battery.state) - property int timeRemaining: debugMode ? debugTimeRemaining : (battery.timeToEmpty || battery.timeToFull) - property bool isCharging: debugMode ? debugIsCharging : (battery.state === UPowerDeviceState.Charging) - property bool isLowBattery: debugMode ? (debugBatteryLevel <= 20) : (battery.percentage <= 20) - property int batteryHealth: debugMode ? debugBatteryHealth : (battery.healthSupported ? Math.round(battery.healthPercentage) : 100) - property string batteryTechnology: { - if (debugMode) return debugBatteryTechnology + /* Native UPower API (commented out - not working correctly, returns 1% instead of actual values) + property bool batteryAvailable: (UPower.displayDevice && UPower.displayDevice.ready && UPower.displayDevice.percentage > 0) || systemBatteryPercentage > 0 + property int batteryLevel: { + if (UPower.displayDevice && UPower.displayDevice.ready && UPower.displayDevice.percentage > 0) { + return Math.round(UPower.displayDevice.percentage) + } + return systemBatteryPercentage + } + property string batteryStatus: { + if (UPower.displayDevice && UPower.displayDevice.ready) { + switch(UPower.displayDevice.state) { + case UPowerDeviceState.Charging: return "Charging" + case UPowerDeviceState.Discharging: return "Discharging" + case UPowerDeviceState.FullyCharged: return "Full" + case UPowerDeviceState.Empty: return "Empty" + case UPowerDeviceState.PendingCharge: return "Pending Charge" + case UPowerDeviceState.PendingDischarge: return "Pending Discharge" + case UPowerDeviceState.Unknown: + default: return "Unknown" + } + } + return systemBatteryState === "charging" ? "Charging" : + systemBatteryState === "discharging" ? "Discharging" : + systemBatteryState === "fully-charged" ? "Full" : + systemBatteryState === "empty" ? "Empty" : "Unknown" + } + property int timeRemaining: (UPower.displayDevice && UPower.displayDevice.ready) ? (UPower.displayDevice.timeToEmpty || UPower.displayDevice.timeToFull || 0) : 0 + property bool isCharging: { + if (UPower.displayDevice && UPower.displayDevice.ready) { + return UPower.displayDevice.state === UPowerDeviceState.Charging + } + return systemBatteryState === "charging" + } + property bool isLowBattery: { + if (UPower.displayDevice && UPower.displayDevice.ready) { + return UPower.displayDevice.percentage <= 20 + } + return systemBatteryPercentage <= 20 + } + */ + property int batteryHealth: 100 // Default fallback + property string batteryTechnology: "Li-ion" // Default fallback + property int cycleCount: 0 // Not implemented for shell fallback + property int batteryCapacity: 45000 // Default fallback + property var powerProfiles: availableProfiles + property string activePowerProfile: "balanced" // Default fallback + + // System battery info from shell command (primary source) + property int systemBatteryPercentage: 100 // Default value, will be updated by shell command + property string systemBatteryState: "charging" // Default value, will be updated by shell command + + // Shell command fallback for battery info + Process { + id: batteryProcess + running: false + command: ["upower", "-i", "/org/freedesktop/UPower/devices/battery_BAT1"] - // Try to get technology from any available laptop battery - for (let i = 0; i < UPower.devices.length; i++) { - let device = UPower.devices[i] - if (device.isLaptopBattery && device.ready) { - // UPower doesn't expose technology directly, but we can get it from the model - let model = device.model || "" - if (model.toLowerCase().includes("li-ion") || model.toLowerCase().includes("lithium")) { - return "Li-ion" - } else if (model.toLowerCase().includes("li-po") || model.toLowerCase().includes("polymer")) { - return "Li-polymer" - } else if (model.toLowerCase().includes("nimh")) { - return "NiMH" + stdout: StdioCollector { + onStreamFinished: { + if (text.trim()) { + let output = text.trim() + let percentageMatch = output.match(/percentage:\s*(\d+)%/) + let stateMatch = output.match(/state:\s*(\w+)/) + + if (percentageMatch) { + root.systemBatteryPercentage = parseInt(percentageMatch[1]) + console.log("Battery percentage updated to:", root.systemBatteryPercentage) + } + if (stateMatch) { + root.systemBatteryState = stateMatch[1] + console.log("Battery state updated to:", root.systemBatteryState) + } } } } - return "Unknown" - } - property int cycleCount: 0 // UPower doesn't expose cycle count - property int batteryCapacity: debugMode ? debugBatteryCapacity : Math.round(battery.energyCapacity * 1000) - property var powerProfiles: availableProfiles - property string activePowerProfile: PowerProfile.toString(PowerProfiles.profile) - - property var battery: UPower.displayDevice - - property var availableProfiles: { - let profiles = [] - if (PowerProfiles.profile !== undefined) { - profiles.push("power-saver") - profiles.push("balanced") - if (PowerProfiles.hasPerformanceProfile) { - profiles.push("performance") + + onExited: (exitCode) => { + if (exitCode !== 0) { + console.warn("Battery process failed with exit code:", exitCode) } } - return profiles } - // Timer to simulate battery changes in debug mode + + // Timer to periodically check battery status Timer { - id: debugTimer - interval: 5000 // Update every 5 seconds - running: debugMode + interval: 5000 // Check every 5 seconds + running: true repeat: true onTriggered: { - // Simulate battery discharge/charge - if (debugIsCharging) { - debugBatteryLevel = Math.min(100, debugBatteryLevel + 1) - if (debugBatteryLevel >= 100) { - debugBatteryStatus = "Full" - debugIsCharging = false - } - } else { - debugBatteryLevel = Math.max(0, debugBatteryLevel - 1) - if (debugBatteryLevel <= 15) { - debugBatteryStatus = "Charging" - debugIsCharging = true - } - } - - // Update time remaining - debugTimeRemaining = debugIsCharging ? - Math.max(0, debugTimeRemaining - 300) : // 5 minutes less to full - Math.max(0, debugTimeRemaining - 300) // 5 minutes less remaining + batteryProcess.running = true } } + Component.onCompleted: { + // Initial battery check + batteryProcess.running = true + // Get current power profile + getCurrentProfile() + console.log("BatteryService initialized with shell command approach") + } + + property var availableProfiles: { + // Try to use power-profiles-daemon via shell command + return ["power-saver", "balanced", "performance"] + } + function setBatteryProfile(profileName) { - let profile = PowerProfile.Balanced + console.log("Setting power profile to:", profileName) + powerProfileProcess.command = ["powerprofilesctl", "set", profileName] + powerProfileProcess.running = true + } + + // Process to set power profile + Process { + id: powerProfileProcess + running: false - if (profileName === "power-saver") { - profile = PowerProfile.PowerSaver - } else if (profileName === "balanced") { - profile = PowerProfile.Balanced - } else if (profileName === "performance") { - if (PowerProfiles.hasPerformanceProfile) { - profile = PowerProfile.Performance + onExited: (exitCode) => { + if (exitCode === 0) { + console.log("Power profile set successfully") + // Update current profile + getCurrentProfile() } else { - console.warn("Performance profile not available") - return + console.warn("Failed to set power profile, exit code:", exitCode) + } + } + } + + // Process to get current power profile + Process { + id: getCurrentProfileProcess + running: false + command: ["powerprofilesctl", "get"] + + stdout: StdioCollector { + onStreamFinished: { + if (text.trim()) { + root.activePowerProfile = text.trim() + console.log("Current power profile:", root.activePowerProfile) + } } - } else { - console.warn("Invalid power profile:", profileName) - return } - console.log("Setting power profile to:", profileName) - PowerProfiles.profile = profile + onExited: (exitCode) => { + if (exitCode !== 0) { + console.warn("Failed to get current power profile, exit code:", exitCode) + } + } + } + + function getCurrentProfile() { + getCurrentProfileProcess.running = true } function getBatteryIcon() {