From a4fb7f4ef39c5ad23a1e4e29e2c0ce82e22b64fd Mon Sep 17 00:00:00 2001 From: Jamer Rebolledo Date: Mon, 13 Apr 2026 15:13:46 -0500 Subject: [PATCH] fix: fallback to sysfs for battery percentage on HID devices --- src/bluetooth.rs | 47 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/src/bluetooth.rs b/src/bluetooth.rs index 54827ae..141755e 100644 --- a/src/bluetooth.rs +++ b/src/bluetooth.rs @@ -6,6 +6,50 @@ use bluer::Device as BTDevice; use crate::app::AppResult; +/// Fallback: read battery percentage from /sys/class/power_supply/ for devices +/// that report battery via kernel drivers (e.g. PS4/PS5 controllers, Nintendo +/// Switch Pro Controllers) instead of the Bluetooth Battery Service (BAS) profile +/// exposed through BlueZ's Battery1 D-Bus interface. +/// +/// First tries to read `capacity` (exact percentage). If unavailable, falls back +/// to `capacity_level` which provides a rough estimate using kernel-defined levels: +/// Unknown, Critical, Low, Normal, High, Full. +/// See: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/include/linux/power_supply.h +fn read_battery_from_sysfs(addr: &Address) -> Option { + let addr_str = addr.to_string().to_lowercase(); + let power_supply_dir = std::path::Path::new("/sys/class/power_supply"); + + if let Ok(entries) = std::fs::read_dir(power_supply_dir) { + for entry in entries.flatten() { + let name = entry.file_name(); + let name_str = name.to_string_lossy(); + if name_str.contains(&addr_str) { + let dir = entry.path(); + + // Try exact percentage first + if let Ok(content) = std::fs::read_to_string(dir.join("capacity")) { + if let Ok(val) = content.trim().parse::() { + return Some(val); + } + } + + // Fall back to capacity_level (e.g. Nintendo Switch Pro Controller) + if let Ok(level) = std::fs::read_to_string(dir.join("capacity_level")) { + return match level.trim().to_lowercase().as_str() { + "full" => Some(100), + "high" => Some(75), + "normal" => Some(50), + "low" => Some(25), + "critical" => Some(5), + _ => None, + }; + } + } + } + } + None +} + #[derive(Debug, Clone)] pub struct Controller { pub adapter: Arc, @@ -114,7 +158,8 @@ impl Controller { let is_trusted = device.is_trusted().await?; let is_connected = device.is_connected().await?; let is_favorite = favorite_devices.contains(&addr); - let battery_percentage = device.battery_percentage().await?; + let battery_percentage = + device.battery_percentage().await?.or_else(|| read_battery_from_sysfs(&addr)); let dev = Device { device,