from gi.repository import GLib from fabric.widgets.label import Label from fabric.widgets.box import Box from fabric.widgets.circularprogressbar import CircularProgressBar from fabric.widgets.button import Button from services.battery import Battery as BatteryService import utils.icons as icons import config.data as data class Battery(Box): def __init__(self, **kwargs): orientation = "v" if data.VERTICAL else "h" super().__init__( name="battery", spacing=0, orientation=orientation, visible=True, **kwargs ) # Initialize battery service self._battery = BatteryService() self._battery.changed.connect(self.update_battery) # Create the battery icon and circular progress self.icon = Label(name="battery-icon", markup=icons.battery) self.circle = CircularProgressBar( name="battery-circle", value=0, size=26, line_width=2, start_angle=150, end_angle=390, style_classes="battery", child=self.icon, ) # Create button to hold the circle self.circle_button = Button( name="battery-button", child=self.circle, ) # Create percentage label self.level = Label( name="battery-level", style_classes="battery", label="0%", ) # Create the container box self.battery_box = Box( name="battery-box", orientation=orientation, spacing=0, children=[self.circle_button], ) # Only add level label in horizontal mode if not data.VERTICAL: self.battery_box.add(self.level) # Enable tooltips self.circle_button.set_has_tooltip(True) self.battery_box.set_has_tooltip(True) self.level.set_has_tooltip(True) # Connect tooltip signals self.circle_button.connect("query-tooltip", self.on_query_tooltip) self.battery_box.connect("query-tooltip", self.on_query_tooltip) self.level.connect("query-tooltip", self.on_query_tooltip) # Add the battery box to self self.add(self.battery_box) # Initial update GLib.idle_add(self.update_battery) def on_query_tooltip(self, _widget, _x, _y, _keyboard_mode, tooltip): """Handle tooltip query""" tooltip.set_markup(self.get_tooltip_text()) return True def get_tooltip_text(self): """Get formatted tooltip text in bullet points""" if not self._battery.is_present: return "No battery detected" percentage = self._battery.percentage state = self._battery.state # Get capacity as integer by converting from float capacity = int(float(self._battery.capacity.rstrip("%"))) tooltip_points = [] # Status and percentage if state == "FULLY_CHARGED": status = f"{icons.bat_full} Fully Charged" elif state == "CHARGING": status = f"{icons.bat_charging} Charging" elif percentage <= 15 and state == "DISCHARGING": status = f"{icons.bat_low} Low Battery" elif state == "DISCHARGING": status = f"{icons.bat_discharging} Discharging" else: status = "Battery" tooltip_points.append(f"• Status: {status}") # Battery level and capacity tooltip_points.append(f"• Level: {int(percentage)}%") tooltip_points.append(f"• Battery Health: {capacity}%") # Add power profile if available power_profile = self._battery.power_profile if power_profile: # Add icon based on profile if power_profile == "power-saver": profile_icon = icons.power_saving elif power_profile == "performance": profile_icon = icons.power_performance else: # balanced profile_icon = icons.power_balanced tooltip_points.append(f"• Profile: {profile_icon} {power_profile}") # Add temperature if available temp = self._battery.temperature if temp != "N/A": tooltip_points.append(f"• Temperature: {temp}") # Add time estimates if state == "CHARGING": time_to_full = self._battery.time_to_full if time_to_full: tooltip_points.append(f"• Time until full: {time_to_full}") elif state == "DISCHARGING": time_to_empty = self._battery.time_to_empty if time_to_empty: tooltip_points.append(f"• Time remaining: {time_to_empty}") return "\n".join(tooltip_points) def update_battery(self, *args): """Update battery status and UI""" if not self._battery.is_present: self.set_visible(False) return True percentage = self._battery.percentage state = self._battery.state charging = state in ["CHARGING", "FULLY_CHARGED"] power_profile = self._battery.power_profile # Update circle progress self.circle.set_value(percentage / 100.0) # Only show percentage label if not at 100% if percentage < 100: self.level.set_label(f"{int(percentage)}%") self.level.set_visible(True) else: self.level.set_visible(False) # Apply alert styling if battery is low AND not charging if percentage <= 15 and not charging: self.icon.add_style_class("alert") self.circle.add_style_class("alert") else: self.icon.remove_style_class("alert") self.circle.remove_style_class("alert") # Update icon based on battery state if not in power profile mode if not power_profile: if state == "FULLY_CHARGED": self.icon.set_markup(icons.battery) elif state == "CHARGING": self.icon.set_markup(icons.charging) elif percentage <= 15 and state == "DISCHARGING": self.icon.set_markup(icons.alert) elif state == "DISCHARGING": self.icon.set_markup(icons.discharging) else: self.icon.set_markup(icons.battery) self.set_visible(True) return True