✨ feat(dock, components, battery): add BatteryPopup with power profile switcher and battery info
This commit is contained in:
3 files changed
+512
-147
No files matched your search
+437
-146
@@ -1,12 +1,383 @@
|
||||
from fabric.widgets.box import Box
|
||||
from fabric.widgets.button import Button
|
||||
from fabric.widgets.circularprogressbar import CircularProgressBar
|
||||
from fabric.widgets.eventbox import EventBox
|
||||
from fabric.widgets.label import Label
|
||||
from fabric.widgets.revealer import Revealer
|
||||
from gi.repository import Gdk, GLib
|
||||
|
||||
import config.data as data
|
||||
import utils.icons as icons
|
||||
from services.battery import Battery as BatteryService
|
||||
from utils.wayland import WaylandWindow as Window
|
||||
|
||||
|
||||
class BatteryPopup(Window):
|
||||
"""Popup window that shows battery information and power profile switcher"""
|
||||
|
||||
def __init__(self, dock_component=None, **kwargs):
|
||||
# Get dock position for proper anchoring
|
||||
dock_position = data.DOCK_POSITION
|
||||
popup_anchor = self._get_popup_anchor(dock_position)
|
||||
margin = self._get_popup_margin(dock_position)
|
||||
|
||||
super().__init__(
|
||||
name="battery-popup",
|
||||
layer="top",
|
||||
anchor=popup_anchor,
|
||||
margin=margin,
|
||||
exclusive=False,
|
||||
keyboard_mode="on-demand",
|
||||
visible=False,
|
||||
all_visible=False,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
self.dock_component = dock_component
|
||||
|
||||
# Get transition type based on dock position
|
||||
transition_type = self._get_revealer_transition(dock_position)
|
||||
|
||||
# Create main content box
|
||||
self.content_box = Box(
|
||||
name="battery-popup-content",
|
||||
orientation="v",
|
||||
spacing=12,
|
||||
style_classes=["battery-popup"],
|
||||
)
|
||||
|
||||
# Create battery info section
|
||||
self.info_box = Box(
|
||||
name="battery-info",
|
||||
orientation="v",
|
||||
spacing=6,
|
||||
)
|
||||
|
||||
# Create power profile switcher section
|
||||
self.profile_box = Box(
|
||||
name="power-profile-switcher",
|
||||
orientation="h",
|
||||
spacing=8,
|
||||
style_classes=["power-profiles"],
|
||||
)
|
||||
|
||||
# Create power profile buttons
|
||||
# We'll determine the actual profile names dynamically
|
||||
self.power_saver_btn = Button(
|
||||
name="power-saver-btn",
|
||||
child=Label(markup=icons.power_saving,name="bat-icon"),
|
||||
on_clicked=lambda *_: self._set_power_profile_by_type("power-saver"),
|
||||
style_classes=["power-profile-btn"],
|
||||
)
|
||||
|
||||
self.balanced_btn = Button(
|
||||
name="balanced-btn",
|
||||
child=Label(markup=icons.power_balanced,name="bat-icon"),
|
||||
on_clicked=lambda *_: self._set_power_profile_by_type("balanced"),
|
||||
style_classes=["power-profile-btn"],
|
||||
)
|
||||
|
||||
self.performance_btn = Button(
|
||||
name="performance-btn",
|
||||
child=Label(markup=icons.power_performance,name="bat-icon"),
|
||||
on_clicked=lambda *_: self._set_power_profile_by_type("performance"),
|
||||
style_classes=["power-profile-btn"],
|
||||
)
|
||||
|
||||
# Add profile buttons to profile box
|
||||
self.profile_box.add(self.power_saver_btn)
|
||||
self.profile_box.add(self.balanced_btn)
|
||||
self.profile_box.add(self.performance_btn)
|
||||
|
||||
# Add sections to content box
|
||||
self.content_box.add(self.info_box)
|
||||
self.content_box.add(self.profile_box)
|
||||
|
||||
# Create revealer for slide transition
|
||||
self.revealer = Revealer(
|
||||
transition_type=transition_type,
|
||||
transition_duration=300,
|
||||
child=self.content_box,
|
||||
)
|
||||
|
||||
self.add(self.revealer)
|
||||
|
||||
# Connect to escape key to close
|
||||
self.connect("key-press-event", self.on_key_press)
|
||||
self.connect("button-press-event", self.on_button_press)
|
||||
self.connect("enter-notify-event", self.on_enter_notify)
|
||||
self.connect("leave-notify-event", self.on_leave_notify)
|
||||
self.set_can_focus(True)
|
||||
|
||||
def _set_power_profile_by_type(self, profile_type):
|
||||
"""Set power profile by type (maps to actual profile names)"""
|
||||
if self.dock_component and hasattr(self.dock_component, '_battery'):
|
||||
try:
|
||||
battery_service = self.dock_component._battery
|
||||
|
||||
# Check if profile proxy is available
|
||||
if not hasattr(battery_service, '_profile_proxy') or not battery_service._profile_proxy:
|
||||
print("Power profile proxy not available")
|
||||
return
|
||||
|
||||
# Get available profiles
|
||||
profiles = battery_service._profile_proxy.Profiles
|
||||
available_profiles = []
|
||||
for p in profiles:
|
||||
if isinstance(p, dict) and "Profile" in p:
|
||||
available_profiles.append(p["Profile"])
|
||||
elif hasattr(p, "Profile"):
|
||||
available_profiles.append(p.Profile)
|
||||
elif isinstance(p, str):
|
||||
available_profiles.append(p)
|
||||
|
||||
# Map profile types to actual profile names
|
||||
profile_mapping = {
|
||||
"power-saver": ["power-saver", "powersave", "power_saver"],
|
||||
"balanced": ["balanced", "balance"],
|
||||
"performance": ["performance", "performance-mode"]
|
||||
}
|
||||
|
||||
# Find the actual profile name
|
||||
actual_profile = None
|
||||
if profile_type in profile_mapping:
|
||||
for candidate in profile_mapping[profile_type]:
|
||||
if candidate in available_profiles:
|
||||
actual_profile = candidate
|
||||
break
|
||||
|
||||
if actual_profile:
|
||||
battery_service._profile_proxy.ActiveProfile = actual_profile
|
||||
self.update_profile_buttons()
|
||||
self.dock_component.update_battery()
|
||||
print(f"Successfully set power profile to {actual_profile}")
|
||||
else:
|
||||
print(f"No matching profile found for type '{profile_type}' in available profiles: {available_profiles}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error setting power profile: {e}")
|
||||
|
||||
def _set_power_profile(self, profile):
|
||||
"""Set the power profile"""
|
||||
if self.dock_component and hasattr(self.dock_component, '_battery'):
|
||||
try:
|
||||
battery_service = self.dock_component._battery
|
||||
|
||||
# Check if profile proxy is available
|
||||
if not hasattr(battery_service, '_profile_proxy') or not battery_service._profile_proxy:
|
||||
print("Power profile proxy not available")
|
||||
return
|
||||
|
||||
# Get available profiles for debugging
|
||||
try:
|
||||
profiles = battery_service._profile_proxy.Profiles
|
||||
available_profiles = []
|
||||
for p in profiles:
|
||||
if isinstance(p, dict) and "Profile" in p:
|
||||
available_profiles.append(p["Profile"])
|
||||
elif hasattr(p, "Profile"):
|
||||
available_profiles.append(p.Profile)
|
||||
elif isinstance(p, str):
|
||||
available_profiles.append(p)
|
||||
|
||||
print(f"Available profiles: {available_profiles}")
|
||||
print(f"Trying to set profile: {profile}")
|
||||
|
||||
# Try to set the profile
|
||||
if profile in available_profiles:
|
||||
battery_service._profile_proxy.ActiveProfile = profile
|
||||
self.update_profile_buttons()
|
||||
# Update the dock component as well
|
||||
self.dock_component.update_battery()
|
||||
print(f"Successfully set power profile to {profile}")
|
||||
else:
|
||||
print(f"Profile '{profile}' not found in available profiles: {available_profiles}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error accessing profiles: {e}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error setting power profile: {e}")
|
||||
|
||||
def update_content(self):
|
||||
"""Update the popup content with current battery information"""
|
||||
if not self.dock_component:
|
||||
return
|
||||
|
||||
# Clear existing info
|
||||
for child in self.info_box.get_children():
|
||||
self.info_box.remove(child)
|
||||
|
||||
battery = self.dock_component._battery
|
||||
|
||||
# Battery percentage and state
|
||||
percentage_label = Label(
|
||||
name="battery-percentage",
|
||||
label=f"{battery.percentage}%",
|
||||
style_classes=["battery-percentage"],
|
||||
)
|
||||
|
||||
state_label = Label(
|
||||
name="battery-state",
|
||||
label=battery.state,
|
||||
style_classes=["battery-state"],
|
||||
)
|
||||
|
||||
# Create percentage and state row
|
||||
percentage_state_box = Box(
|
||||
name="percentage-state-box",
|
||||
orientation="h",
|
||||
spacing=8,
|
||||
children=[percentage_label, state_label],
|
||||
)
|
||||
|
||||
# Battery capacity (health)
|
||||
capacity_box = Box(
|
||||
name="capacity-box",
|
||||
orientation="h",
|
||||
spacing=8,
|
||||
children=[
|
||||
Label(label="Battery Health:", style_classes=["battery-label"]),
|
||||
Label(label=battery.capacity, style_classes=["battery-value"]),
|
||||
],
|
||||
)
|
||||
|
||||
# Time information based on charging state
|
||||
time_box = None
|
||||
if battery.state == "CHARGING":
|
||||
time_to_full = battery.time_to_full
|
||||
if time_to_full and time_to_full != "0m":
|
||||
time_box = Box(
|
||||
name="time-box",
|
||||
orientation="h",
|
||||
spacing=8,
|
||||
children=[
|
||||
Label(label="Time to full:", style_classes=["battery-label"]),
|
||||
Label(label=time_to_full, style_classes=["battery-value"]),
|
||||
],
|
||||
)
|
||||
else: # DISCHARGING or other states
|
||||
time_to_empty = battery.time_to_empty
|
||||
if time_to_empty and time_to_empty != "0m":
|
||||
time_box = Box(
|
||||
name="time-box",
|
||||
orientation="h",
|
||||
spacing=8,
|
||||
children=[
|
||||
Label(label="Time remaining:", style_classes=["battery-label"]),
|
||||
Label(label=time_to_empty, style_classes=["battery-value"]),
|
||||
],
|
||||
)
|
||||
|
||||
# Power profile information
|
||||
profile_label_box = Box(
|
||||
name="profile-label-box",
|
||||
orientation="h",
|
||||
spacing=8,
|
||||
children=[
|
||||
Label(label="Power Profile:", style_classes=["battery-label"]),
|
||||
Label(label=battery.power_profile.title(), style_classes=["battery-value"]),
|
||||
],
|
||||
)
|
||||
|
||||
# Add all info to the info box
|
||||
self.info_box.add(percentage_state_box)
|
||||
self.info_box.add(capacity_box)
|
||||
if time_box:
|
||||
self.info_box.add(time_box)
|
||||
self.info_box.add(profile_label_box)
|
||||
|
||||
# Update profile button states
|
||||
self.update_profile_buttons()
|
||||
|
||||
def update_profile_buttons(self):
|
||||
"""Update the active state of profile buttons"""
|
||||
if not self.dock_component or not hasattr(self.dock_component, '_battery'):
|
||||
return
|
||||
|
||||
current_profile = self.dock_component._battery.power_profile
|
||||
|
||||
# Remove active class from all buttons
|
||||
self.power_saver_btn.remove_style_class("active")
|
||||
self.balanced_btn.remove_style_class("active")
|
||||
self.performance_btn.remove_style_class("active")
|
||||
|
||||
# Add active class to current profile button
|
||||
if current_profile == "power-saver":
|
||||
self.power_saver_btn.add_style_class("active")
|
||||
elif current_profile == "performance":
|
||||
self.performance_btn.add_style_class("active")
|
||||
else: # balanced or default
|
||||
self.balanced_btn.add_style_class("active")
|
||||
|
||||
def on_key_press(self, widget, event):
|
||||
"""Handle key press events"""
|
||||
if event.keyval == Gdk.KEY_Escape:
|
||||
self.hide_popup()
|
||||
return True
|
||||
return False
|
||||
|
||||
def show_popup(self):
|
||||
"""Show the battery popup"""
|
||||
self.update_content()
|
||||
self.set_visible(True)
|
||||
self.revealer.reveal()
|
||||
self.grab_focus()
|
||||
|
||||
def _get_popup_anchor(self, dock_position):
|
||||
"""Get popup anchor based on dock position"""
|
||||
anchor_map = {
|
||||
"Top": "top",
|
||||
"Bottom": "bottom right",
|
||||
"Left": "left",
|
||||
"Right": "right",
|
||||
}
|
||||
return anchor_map.get(dock_position, "bottom")
|
||||
|
||||
def _get_popup_margin(self, dock_position):
|
||||
"""Get popup margin based on dock position"""
|
||||
margin_map = {
|
||||
"Top": "60px 10px 10px 10px",
|
||||
"Bottom": "10px 650px 60px 10px",
|
||||
"Left": "10px 10px 10px 60px",
|
||||
"Right": "10px 60px 10px 10px",
|
||||
}
|
||||
return margin_map.get(dock_position, "10px 10px 60px 10px")
|
||||
|
||||
def _get_revealer_transition(self, dock_position):
|
||||
"""Get revealer transition type based on dock position"""
|
||||
transition_map = {
|
||||
"Top": "slide-down",
|
||||
"Bottom": "slide-up",
|
||||
"Left": "slide-right",
|
||||
"Right": "slide-left",
|
||||
}
|
||||
return transition_map.get(dock_position, "slide-up")
|
||||
|
||||
def on_button_press(self, widget, event):
|
||||
"""Handle button press events"""
|
||||
return False
|
||||
|
||||
def on_enter_notify(self, widget, event):
|
||||
"""Handle mouse entering popup"""
|
||||
# Cancel any pending hide from dock component
|
||||
if self.dock_component and hasattr(self.dock_component, '_hover_timeout'):
|
||||
if self.dock_component._hover_timeout:
|
||||
GLib.source_remove(self.dock_component._hover_timeout)
|
||||
self.dock_component._hover_timeout = None
|
||||
return False
|
||||
|
||||
def on_leave_notify(self, widget, event):
|
||||
"""Handle mouse leaving popup"""
|
||||
# Hide popup when mouse leaves
|
||||
self.hide_popup()
|
||||
return False
|
||||
|
||||
def hide_popup(self):
|
||||
"""Hide the battery popup"""
|
||||
self.revealer.unreveal()
|
||||
# Hide window after transition completes
|
||||
GLib.timeout_add(350, lambda: self.set_visible(False))
|
||||
|
||||
|
||||
class Battery(Box):
|
||||
@@ -36,14 +407,16 @@ class Battery(Box):
|
||||
child=self.circle,
|
||||
)
|
||||
|
||||
self.circle_button.add_events(
|
||||
Gdk.EventMask.ENTER_NOTIFY_MASK
|
||||
| Gdk.EventMask.LEAVE_NOTIFY_MASK
|
||||
| Gdk.EventMask.BUTTON_PRESS_MASK
|
||||
# Create event box for hover detection
|
||||
self.event_box = EventBox(
|
||||
events=["enter-notify", "leave-notify"],
|
||||
child=self.circle_button,
|
||||
)
|
||||
self.circle_button.connect("enter-notify-event", self.on_button_enter)
|
||||
self.circle_button.connect("leave-notify-event", self.on_button_leave)
|
||||
self.circle_button.connect("button-press-event", self.on_button_press)
|
||||
|
||||
# Connect hover events
|
||||
self.event_box.connect("enter-notify-event", self.on_enter_notify)
|
||||
self.event_box.connect("leave-notify-event", self.on_leave_notify)
|
||||
self.event_box.add_events(Gdk.EventMask.ENTER_NOTIFY_MASK | Gdk.EventMask.LEAVE_NOTIFY_MASK)
|
||||
|
||||
self.level = Label(
|
||||
name="battery-level",
|
||||
@@ -55,121 +428,71 @@ class Battery(Box):
|
||||
name="battery-box",
|
||||
orientation=orientation,
|
||||
spacing=0,
|
||||
children=[self.circle_button],
|
||||
children=[self.event_box],
|
||||
)
|
||||
|
||||
if not data.VERTICAL:
|
||||
self.battery_box.add(self.level)
|
||||
|
||||
self.circle_button.set_has_tooltip(True)
|
||||
self.battery_box.set_has_tooltip(True)
|
||||
self.level.set_has_tooltip(True)
|
||||
|
||||
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)
|
||||
|
||||
self.add(self.battery_box)
|
||||
|
||||
self.popup = None
|
||||
|
||||
# Hover timeout for debouncing
|
||||
self._hover_timeout = None
|
||||
|
||||
# Connect to destroy signal for cleanup
|
||||
self.connect("destroy", self.on_destroy)
|
||||
|
||||
GLib.idle_add(self.update_battery)
|
||||
|
||||
def on_button_enter(self, widget, event):
|
||||
window = widget.get_window()
|
||||
if window:
|
||||
window.set_cursor(Gdk.Cursor(Gdk.CursorType.HAND2))
|
||||
def on_destroy(self, widget):
|
||||
"""Clean up popup when component is destroyed"""
|
||||
try:
|
||||
# Remove hover timeout
|
||||
if hasattr(self, "_hover_timeout") and self._hover_timeout:
|
||||
GLib.source_remove(self._hover_timeout)
|
||||
self._hover_timeout = None
|
||||
|
||||
def on_button_leave(self, widget, event):
|
||||
window = widget.get_window()
|
||||
if window:
|
||||
window.set_cursor(None)
|
||||
# Clean up popup
|
||||
if hasattr(self, 'popup') and self.popup:
|
||||
try:
|
||||
self.popup.destroy()
|
||||
self.popup = None
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def on_button_press(self, widget, event):
|
||||
if event.button == 1: # Left click
|
||||
try:
|
||||
if (
|
||||
not hasattr(self._battery, "_profile_proxy")
|
||||
or not self._battery._profile_proxy
|
||||
):
|
||||
return False
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
current_profile = self._battery._profile_proxy.ActiveProfile
|
||||
profiles = self._battery._profile_proxy.Profiles
|
||||
profile_names = []
|
||||
def on_enter_notify(self, widget, event):
|
||||
"""Handle mouse entering the battery component"""
|
||||
# Cancel any pending hover timeout
|
||||
if self._hover_timeout:
|
||||
GLib.source_remove(self._hover_timeout)
|
||||
self._hover_timeout = None
|
||||
|
||||
for profile in profiles:
|
||||
if isinstance(profile, dict) and "Profile" in profile:
|
||||
profile_names.append(profile["Profile"])
|
||||
elif hasattr(profile, "Profile"):
|
||||
profile_names.append(profile.Profile)
|
||||
elif isinstance(profile, str):
|
||||
profile_names.append(profile)
|
||||
|
||||
if profile_names and current_profile in profile_names:
|
||||
next_index = (profile_names.index(current_profile) + 1) % len(
|
||||
profile_names
|
||||
)
|
||||
next_profile = profile_names[next_index]
|
||||
self._battery._profile_proxy.ActiveProfile = next_profile
|
||||
else:
|
||||
return False
|
||||
except Exception:
|
||||
return False
|
||||
return True
|
||||
# Show popup immediately on hover
|
||||
if not self.popup or not self.popup.get_visible():
|
||||
# Create popup on-demand if it doesn't exist
|
||||
if not self.popup:
|
||||
self.popup = BatteryPopup(dock_component=self)
|
||||
self.popup.show_popup()
|
||||
return False
|
||||
|
||||
def on_query_tooltip(self, widget, x, y, keyboard_mode, tooltip):
|
||||
tooltip.set_markup(self.get_tooltip_text())
|
||||
return True
|
||||
|
||||
def get_tooltip_text(self):
|
||||
if not self._battery.is_present:
|
||||
return "No battery detected"
|
||||
|
||||
percentage = self._battery.percentage
|
||||
state = self._battery.state
|
||||
capacity = int(float(self._battery.capacity.rstrip("%")))
|
||||
tooltip_points = []
|
||||
|
||||
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}")
|
||||
|
||||
tooltip_points.append(f"• Level: {int(percentage)}%")
|
||||
tooltip_points.append(f"• Battery Health: {capacity}%")
|
||||
|
||||
power_profile = self._battery.power_profile
|
||||
if power_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}")
|
||||
|
||||
temp = self._battery.temperature
|
||||
if temp != "N/A":
|
||||
tooltip_points.append(f"• Temperature: {temp}")
|
||||
|
||||
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 on_leave_notify(self, widget, event):
|
||||
"""Handle mouse leaving the battery component"""
|
||||
# Hide popup with a small delay to prevent flickering
|
||||
self._hover_timeout = GLib.timeout_add(100, self._hide_popup_delayed)
|
||||
return False
|
||||
|
||||
def _hide_popup_delayed(self):
|
||||
"""Hide popup after delay"""
|
||||
if self.popup and self.popup.get_visible():
|
||||
self.popup.hide_popup()
|
||||
self._hover_timeout = None
|
||||
return False # Don't repeat timeout
|
||||
|
||||
def update_battery(self, *args):
|
||||
if not self._battery.is_present:
|
||||
self.set_visible(False)
|
||||
@@ -178,7 +501,6 @@ class Battery(Box):
|
||||
percentage = self._battery.percentage
|
||||
state = self._battery.state
|
||||
charging = state in ["CHARGING", "FULLY_CHARGED"]
|
||||
power_profile = self._battery.power_profile
|
||||
|
||||
self.circle.set_value(percentage / 100.0)
|
||||
|
||||
@@ -188,50 +510,19 @@ class Battery(Box):
|
||||
else:
|
||||
self.level.set_visible(False)
|
||||
|
||||
self.circle.remove_style_class("power-saver")
|
||||
self.circle.remove_style_class("performance")
|
||||
self.circle.remove_style_class("balanced")
|
||||
self.circle.remove_style_class("discharging")
|
||||
self.circle.remove_style_class("discharging-low")
|
||||
self.circle.remove_style_class("discharging-critical")
|
||||
|
||||
self.icon.remove_style_class("discharging")
|
||||
self.icon.remove_style_class("discharging-low")
|
||||
self.icon.remove_style_class("discharging-critical")
|
||||
|
||||
if power_profile:
|
||||
self.circle.add_style_class(power_profile)
|
||||
if power_profile == "power-saver":
|
||||
self.icon.set_markup(icons.power_saving)
|
||||
elif power_profile == "performance":
|
||||
self.icon.set_markup(icons.power_performance)
|
||||
else: # balanced
|
||||
# In balanced mode, show discharging icons when discharging
|
||||
if state == "DISCHARGING":
|
||||
if percentage <= 15:
|
||||
self.icon.set_markup(icons.bat_low)
|
||||
self.circle.add_style_class("discharging-critical")
|
||||
self.icon.add_style_class("discharging-critical")
|
||||
elif percentage <= 30:
|
||||
self.icon.set_markup(icons.alert)
|
||||
self.circle.add_style_class("discharging-low")
|
||||
self.icon.add_style_class("discharging-low")
|
||||
else:
|
||||
self.icon.set_markup(icons.bat_discharging)
|
||||
self.circle.add_style_class("discharging")
|
||||
self.icon.add_style_class("discharging")
|
||||
elif state == "CHARGING":
|
||||
self.icon.set_markup(icons.charging)
|
||||
else:
|
||||
self.icon.set_markup(icons.battery)
|
||||
if state == "FULLY_CHARGED":
|
||||
self.icon.set_markup(icons.battery)
|
||||
elif state == "CHARGING":
|
||||
self.icon.set_markup(icons.charging)
|
||||
elif state == "DISCHARGING":
|
||||
self.icon.set_markup(icons.discharging)
|
||||
else:
|
||||
if state == "FULLY_CHARGED":
|
||||
self.icon.set_markup(icons.battery)
|
||||
elif state == "CHARGING":
|
||||
self.icon.set_markup(icons.charging)
|
||||
elif state == "DISCHARGING":
|
||||
self.icon.set_markup(icons.discharging)
|
||||
else:
|
||||
self.icon.set_markup(icons.battery)
|
||||
self.icon.set_markup(icons.battery)
|
||||
|
||||
if percentage <= 15 and not charging:
|
||||
self.icon.add_style_class("alert")
|
||||
|
||||
+1
-1
@@ -55,7 +55,7 @@ class Battery(Service):
|
||||
|
||||
@Property(str, "readable")
|
||||
def capacity(self):
|
||||
return f"{self._battery.Capacity}%"
|
||||
return f"{int(self._battery.Capacity)}%"
|
||||
|
||||
@Property(bool, "readable", default_value=False)
|
||||
def is_present(self):
|
||||
|
||||
@@ -106,6 +106,80 @@
|
||||
color: var(--red-dim);
|
||||
}
|
||||
|
||||
/*
|
||||
* ── battery popup ─────────────────────────────────────────────────────
|
||||
*/
|
||||
|
||||
#battery-popup-content {
|
||||
background-color: var(--shadow);
|
||||
border: 1px solid var(--surface);
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
margin: 4px;
|
||||
}
|
||||
|
||||
.power-profiles {
|
||||
padding-top: 12px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
#bat-icon {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.power-profile-btn {
|
||||
background-color: var(--surface);
|
||||
border-radius: 8px;
|
||||
padding: 10px 20px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
.power-profile-btn:hover,
|
||||
.power-profile-btn:focus {
|
||||
background-color: var(--surface-bright);
|
||||
}
|
||||
|
||||
.power-profile-btn.active {
|
||||
background-color: var(--primary);
|
||||
}
|
||||
|
||||
.power-profile-btn.active #bat-icon {
|
||||
color: var(--shadow);
|
||||
}
|
||||
|
||||
/* Battery info styles */
|
||||
.battery-percentage {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.battery-state {
|
||||
font-size: 14px;
|
||||
color: var(--text-dim);
|
||||
}
|
||||
|
||||
.battery-label {
|
||||
font-size: 12px;
|
||||
color: var(--text-dim);
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
.battery-value {
|
||||
font-size: 12px;
|
||||
color: var(--text);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
#percentage-state-box {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
#capacity-box,
|
||||
#time-box,
|
||||
#profile-label-box {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
/*
|
||||
* ── metrics ───────────────────────────────────────────────────────────
|
||||
*/
|
||||
|
||||
Reference in new issue
Block a user