Huge Updates
This commit is contained in:
1 parent
28f47aed3c
commit
1885a2dc30
30 files changed
+1421
-1260
No files matched your search
@@ -1,10 +1,6 @@
|
||||
from .battery import *
|
||||
from .bluetooth import *
|
||||
from .idle import *
|
||||
from .stats import *
|
||||
from .microphone import *
|
||||
from .network import *
|
||||
from .powerprofile import *
|
||||
from .taskbar import *
|
||||
from .volume import *
|
||||
from .workspace import *
|
||||
from .indicators import *
|
||||
from .updates import *
|
||||
@@ -1,70 +1,237 @@
|
||||
import psutil
|
||||
from fabric import Fabricator
|
||||
import subprocess
|
||||
import re
|
||||
from fabric.widgets.box import Box
|
||||
from fabric.widgets.overlay import Overlay
|
||||
from fabric.widgets.eventbox import EventBox
|
||||
from fabric.widgets.button import Button
|
||||
from fabric.widgets.label import Label
|
||||
from fabric.widgets.circularprogressbar import CircularProgressBar
|
||||
from snippets import MaterialIcon
|
||||
from fabric.widgets.overlay import Overlay
|
||||
from fabric.widgets.revealer import Revealer
|
||||
from fabric.core.fabricator import Fabricator
|
||||
from fabric.utils.helpers import exec_shell_command_async
|
||||
from gi.repository import GLib
|
||||
import snippets.iconss as icons
|
||||
|
||||
|
||||
class BatteryLabel(Box):
|
||||
ICONS_CHARGING = [
|
||||
"battery_charging_20",
|
||||
"battery_charging_20",
|
||||
"battery_charging_20",
|
||||
"battery_charging_30",
|
||||
"battery_charging_30",
|
||||
"battery_charging_50",
|
||||
"battery_charging_60",
|
||||
"battery_charging_80",
|
||||
"battery_charging_80",
|
||||
"battery_charging_90",
|
||||
"battery_charging_full",
|
||||
]
|
||||
ICONS_NOT_CHARGING = [
|
||||
"battery_alert",
|
||||
"battery_1_bar",
|
||||
"battery_1_bar",
|
||||
"battery_2_bar",
|
||||
"battery_2_bar",
|
||||
"battery_3_bar",
|
||||
"battery_4_bar",
|
||||
"battery_4_bar",
|
||||
"battery_5_bar",
|
||||
"battery_6_bar",
|
||||
"battery_full",
|
||||
]
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
Fabricator(interval=1000, poll_from=self.update_battery_status)
|
||||
|
||||
def update_battery_status(self, *_):
|
||||
battery = psutil.sensors_battery()
|
||||
if battery is None:
|
||||
self.hide()
|
||||
return
|
||||
is_charging = battery.power_plugged if battery else False
|
||||
icons = self.ICONS_CHARGING if is_charging else self.ICONS_NOT_CHARGING
|
||||
battery_percent = round(battery.percent)
|
||||
index = min(max(battery_percent // 10, 0), 10)
|
||||
battery_icon = MaterialIcon(icons[index], size=16)
|
||||
|
||||
self.progress_bar = CircularProgressBar(
|
||||
name="progress", line_style="round", line_width=2, size=24
|
||||
super().__init__(
|
||||
name="battery",
|
||||
orientation="h",
|
||||
spacing=0,
|
||||
)
|
||||
|
||||
self.battery_overlay = Overlay(child=self.progress_bar, overlays=battery_icon)
|
||||
self.battery_label = Label(label=f"{battery_percent}%", name="battery-label")
|
||||
|
||||
self.children = self.battery_label, self.battery_overlay
|
||||
|
||||
battery_percent = round(battery.percent)
|
||||
self.progress_bar.value = battery_percent / 100
|
||||
self.show() if battery_percent < 100 else self.hide()
|
||||
|
||||
self.set_tooltip_text(
|
||||
f"Battery: {battery_percent}%\nStatus: {'Charging' if is_charging else 'Discharging'}"
|
||||
self.bat_save = Button(
|
||||
name="battery-save",
|
||||
child=Label(name="battery-save-label", markup=icons.power_saving),
|
||||
on_clicked=lambda *_: self.set_power_mode("powersave"),
|
||||
)
|
||||
self.bat_balanced = Button(
|
||||
name="battery-balanced",
|
||||
child=Label(name="battery-balanced-label", markup=icons.power_balanced),
|
||||
on_clicked=lambda *_: self.set_power_mode("balanced"),
|
||||
)
|
||||
self.bat_perf = Button(
|
||||
name="battery-performance",
|
||||
child=Label(
|
||||
name="battery-performance-label", markup=icons.power_performance
|
||||
),
|
||||
on_clicked=lambda *_: self.set_power_mode("performance"),
|
||||
)
|
||||
|
||||
return True
|
||||
# Attach mouse enter/leave events to buttons as well
|
||||
for btn in [self.bat_save, self.bat_balanced, self.bat_perf]:
|
||||
btn.connect("enter-notify-event", self.on_mouse_enter)
|
||||
btn.connect("leave-notify-event", self.on_mouse_leave)
|
||||
|
||||
# Group the mode buttons into a container.
|
||||
self.mode_switcher = Box(
|
||||
name="power-mode-switcher",
|
||||
orientation="h",
|
||||
spacing=4,
|
||||
children=[self.bat_save, self.bat_balanced, self.bat_perf],
|
||||
)
|
||||
|
||||
self.bat_icon = Label(name="battery-icon", markup=icons.battery)
|
||||
|
||||
self.bat_circle = CircularProgressBar(
|
||||
name="battery-circle",
|
||||
value=0,
|
||||
size=28,
|
||||
line_width=2,
|
||||
start_angle=180,
|
||||
end_angle=360,
|
||||
)
|
||||
|
||||
self.bat_overlay = Overlay(
|
||||
name="battery-overlay",
|
||||
visible=False,
|
||||
child=self.bat_circle,
|
||||
overlays=[self.bat_icon],
|
||||
)
|
||||
|
||||
self.bat_level = Label(
|
||||
name="battery-level",
|
||||
label="100%",
|
||||
)
|
||||
|
||||
self.bat_revealer = Revealer(
|
||||
name="battery-revealer",
|
||||
transition_duration=250,
|
||||
transition_type="slide-left",
|
||||
child=self.bat_level,
|
||||
child_revealed=False,
|
||||
)
|
||||
|
||||
# Create an inner container to hold the battery elements.
|
||||
inner_container = Box(orientation="h", spacing=3)
|
||||
inner_container.add(self.bat_overlay)
|
||||
inner_container.add(self.bat_revealer)
|
||||
inner_container.add(self.mode_switcher)
|
||||
|
||||
# Wrap the inner container in an EventBox.
|
||||
self.event_box = EventBox(
|
||||
events=["enter-notify-event", "leave-notify-event"],
|
||||
name="battery-eventbox",
|
||||
)
|
||||
self.event_box.connect("enter-notify-event", self.on_mouse_enter)
|
||||
self.event_box.connect("leave-notify-event", self.on_mouse_leave)
|
||||
self.event_box.add(inner_container)
|
||||
|
||||
# Add the EventBox to the Battery widget.
|
||||
self.add(self.event_box)
|
||||
|
||||
self.current_mode = None
|
||||
# Initialize hide_timer for delayed hiding.
|
||||
self.hide_timer = None
|
||||
# Initialize counter to track hover status across all related widgets.
|
||||
self.hover_counter = 0
|
||||
|
||||
# Initialize Fabricator for battery polling every 5 seconds.
|
||||
self.batt_fabricator = Fabricator(
|
||||
lambda *args, **kwargs: self.poll_battery(),
|
||||
interval=1000,
|
||||
stream=False,
|
||||
default_value=0,
|
||||
)
|
||||
self.batt_fabricator.changed.connect(self.update_battery)
|
||||
|
||||
# Run update_battery immediately at startup.
|
||||
GLib.idle_add(self.update_battery, None, self.poll_battery())
|
||||
self.set_power_mode("balanced")
|
||||
|
||||
def on_mouse_enter(self, widget, event):
|
||||
"""Reveal battery level on hover."""
|
||||
self.hover_counter += 1
|
||||
if self.hide_timer is not None:
|
||||
GLib.source_remove(self.hide_timer)
|
||||
self.hide_timer = None
|
||||
self.bat_revealer.set_reveal_child(True)
|
||||
return False
|
||||
|
||||
def on_mouse_leave(self, widget, event):
|
||||
"""Schedule hiding the battery level after a 0.5s delay only if not hovering any element."""
|
||||
if self.hover_counter > 0:
|
||||
self.hover_counter -= 1
|
||||
if self.hover_counter == 0:
|
||||
if self.hide_timer is not None:
|
||||
GLib.source_remove(self.hide_timer)
|
||||
self.hide_timer = GLib.timeout_add(500, self.hide_revealer)
|
||||
return False
|
||||
|
||||
def hide_revealer(self):
|
||||
self.bat_revealer.set_reveal_child(False)
|
||||
self.hide_timer = None
|
||||
return False
|
||||
|
||||
def poll_battery(self):
|
||||
"""
|
||||
Polls the battery status by running the "acpi -b" command.
|
||||
If no battery information is found, returns (0, None).
|
||||
Otherwise, returns a tuple: (battery percentage as a float between 0 and 1, battery status string)
|
||||
"""
|
||||
try:
|
||||
output = subprocess.check_output(["acpi", "-b"]).decode("utf-8").strip()
|
||||
if "Battery" not in output:
|
||||
return (0, None)
|
||||
match_percent = re.search(r"(\d+)%", output)
|
||||
match_status = re.search(r"Battery \d+: (\w+)", output)
|
||||
if match_percent:
|
||||
percent = int(match_percent.group(1))
|
||||
status = match_status.group(1) if match_status else None
|
||||
return (percent / 100.0, status)
|
||||
except Exception:
|
||||
pass
|
||||
return (0, None)
|
||||
|
||||
def update_battery(self, sender, battery_data):
|
||||
"""
|
||||
Updates the battery widget...
|
||||
"""
|
||||
value, status = battery_data
|
||||
if value == 0:
|
||||
self.bat_overlay.set_visible(False)
|
||||
self.bat_revealer.set_visible(False)
|
||||
else:
|
||||
self.bat_overlay.set_visible(True)
|
||||
self.bat_revealer.set_visible(True)
|
||||
self.bat_circle.set_value(value)
|
||||
|
||||
percentage = int(value * 100)
|
||||
self.bat_level.set_label(f"{percentage}%")
|
||||
|
||||
# Actualizar el icono de la batería basado en el nivel y el estado de carga.
|
||||
if percentage <= 15:
|
||||
self.bat_icon.set_markup(icons.alert)
|
||||
self.bat_icon.add_style_class("alert")
|
||||
self.bat_circle.add_style_class("alert")
|
||||
else:
|
||||
self.bat_icon.remove_style_class("alert")
|
||||
self.bat_circle.remove_style_class("alert")
|
||||
if (
|
||||
status == "Discharging"
|
||||
): # Muestra discharging en cualquier nivel, incluso al 100%.
|
||||
self.bat_icon.set_markup(icons.discharging)
|
||||
elif percentage == 100:
|
||||
self.bat_icon.set_markup(icons.battery)
|
||||
elif status == "Charging":
|
||||
self.bat_icon.set_markup(icons.charging)
|
||||
else:
|
||||
self.bat_icon.set_markup(icons.battery)
|
||||
|
||||
def set_power_mode(self, mode):
|
||||
"""
|
||||
Switches power mode by running the corresponding auto-cpufreq command.
|
||||
mode: one of 'powersave', 'balanced', or 'performance'
|
||||
"""
|
||||
commands = {
|
||||
"powersave": "sudo auto-cpufreq --force powersave",
|
||||
"balanced": "sudo auto-cpufreq --force reset",
|
||||
"performance": "sudo auto-cpufreq --force performance",
|
||||
}
|
||||
if mode in commands:
|
||||
try:
|
||||
exec_shell_command_async(commands[mode])
|
||||
self.current_mode = mode
|
||||
self.update_button_styles()
|
||||
except Exception as err:
|
||||
# Optionally, handle errors or display a notification.
|
||||
print(f"Error setting power mode: {err}")
|
||||
|
||||
def update_button_styles(self):
|
||||
"""
|
||||
Optionally updates button styles to reflect the current mode.
|
||||
Adjust the styling method based on your toolkit's capabilities.
|
||||
"""
|
||||
if self.current_mode == "powersave":
|
||||
self.bat_save.add_style_class("active")
|
||||
self.bat_balanced.remove_style_class("active")
|
||||
self.bat_perf.remove_style_class("active")
|
||||
elif self.current_mode == "balanced":
|
||||
self.bat_save.remove_style_class("active")
|
||||
self.bat_balanced.add_style_class("active")
|
||||
self.bat_perf.remove_style_class("active")
|
||||
elif self.current_mode == "performance":
|
||||
self.bat_save.remove_style_class("active")
|
||||
self.bat_balanced.remove_style_class("active")
|
||||
self.bat_perf.add_style_class("active")
|
||||
@@ -1,34 +0,0 @@
|
||||
from fabric.bluetooth import BluetoothClient
|
||||
from fabric.widgets.box import Box
|
||||
from snippets import MaterialIcon
|
||||
|
||||
|
||||
class BluetoothIndicator(Box):
|
||||
ICON_ENABLED = "bluetooth"
|
||||
ICON_DISABLED = "bluetooth_disabled"
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.bluetooth_client = BluetoothClient()
|
||||
|
||||
self.bluetooth_icon = None
|
||||
|
||||
self.bluetooth_client.connect("changed", self.update_bluetooth_status)
|
||||
|
||||
self.update_bluetooth_status()
|
||||
|
||||
def update_bluetooth_status(self, *_):
|
||||
icon_label = (
|
||||
self.ICON_ENABLED if self.bluetooth_client.enabled else self.ICON_DISABLED
|
||||
)
|
||||
|
||||
new_icon = MaterialIcon(icon_label, size=16)
|
||||
|
||||
if self.bluetooth_icon:
|
||||
self.remove(self.bluetooth_icon)
|
||||
|
||||
self.bluetooth_icon = new_icon
|
||||
self.add(self.bluetooth_icon)
|
||||
|
||||
def on_destroy(self):
|
||||
self.bluetooth_client.disconnect("changed", self.update_bluetooth_status)
|
||||
@@ -0,0 +1,134 @@
|
||||
import subprocess
|
||||
from fabric import Fabricator
|
||||
from fabric.widgets.label import Label
|
||||
from fabric.widgets.box import Box
|
||||
from fabric.bluetooth import BluetoothClient
|
||||
from services import NetworkClient, audio
|
||||
import snippets.iconss as icons
|
||||
|
||||
|
||||
class Indicators(Box):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(orientation="h", spacing=2, **kwargs)
|
||||
|
||||
self.bluetooth_icon = Label(name="indicator-icon")
|
||||
self.wifi_icon = Label(name="indicator-icon")
|
||||
self.volume_icon_button = Label(name="indicator-icon")
|
||||
self.microphone_icon = Label(name="indicator-icon")
|
||||
self.idle_label = Label(name="indicator-icon")
|
||||
self.night_label = Label(name="indicator-icon")
|
||||
|
||||
for widget in [
|
||||
self.bluetooth_icon,
|
||||
self.wifi_icon,
|
||||
self.volume_icon_button,
|
||||
self.microphone_icon,
|
||||
self.idle_label,
|
||||
self.night_label,
|
||||
]:
|
||||
self.add(widget)
|
||||
|
||||
self.audio_service = audio
|
||||
self.bluetooth_client = BluetoothClient()
|
||||
self.network_client = NetworkClient()
|
||||
|
||||
# Connect signals
|
||||
self.bluetooth_client.connect("changed", self.update_bluetooth_status)
|
||||
self.audio_service.connect("microphone_changed", self.update_mic_status)
|
||||
self.audio_service.connect("changed", self.update_volume_status)
|
||||
self.network_client.connect("device_ready", self.update_network_status)
|
||||
|
||||
if self.network_client.wifi_device:
|
||||
self.network_client.wifi_device.connect(
|
||||
"changed", self.update_network_status
|
||||
)
|
||||
|
||||
if self.network_client.ethernet_device:
|
||||
self.network_client.ethernet_device.connect(
|
||||
"changed", self.update_network_status
|
||||
)
|
||||
|
||||
Fabricator(interval=1000, poll_from=self.update_all_statuses)
|
||||
|
||||
self.update_bluetooth_status()
|
||||
|
||||
def update_all_statuses(self, *_args):
|
||||
self.update_idle_night_status()
|
||||
self.update_network_status()
|
||||
|
||||
def update_idle_night_status(self, *_):
|
||||
"""Updates the idle and night mode status icons."""
|
||||
self.idle_label.set_markup(
|
||||
icons.coffee
|
||||
if subprocess.run(
|
||||
["pgrep", "-x", "wlinhibit"], capture_output=True
|
||||
).returncode
|
||||
== 0
|
||||
else ""
|
||||
)
|
||||
|
||||
self.night_label.set_markup(
|
||||
icons.night
|
||||
if subprocess.run(
|
||||
["pgrep", "-x", "hyprsunset"], capture_output=True
|
||||
).returncode
|
||||
== 0
|
||||
else ""
|
||||
)
|
||||
|
||||
def update_volume_status(self, *_):
|
||||
"""Updates the volume icon based on speaker status."""
|
||||
stream = self.audio_service.speaker
|
||||
if stream:
|
||||
volume_level = stream.volume
|
||||
self.volume_icon_button.set_markup(
|
||||
icons.vol_off
|
||||
if stream.muted
|
||||
else (
|
||||
icons.vol_mute
|
||||
if volume_level == 0
|
||||
else (icons.vol_medium if volume_level <= 50 else icons.vol_high)
|
||||
)
|
||||
)
|
||||
|
||||
def update_mic_status(self, *_):
|
||||
"""Updates the microphone icon based on mute status."""
|
||||
mic = self.audio_service.microphone
|
||||
self.microphone_icon.set_markup(icons.mic_off if mic and mic.muted else "")
|
||||
self.microphone_icon.set_visible(bool(mic))
|
||||
|
||||
def update_bluetooth_status(self, *_):
|
||||
"""Updates the Bluetooth icon based on enabled status."""
|
||||
self.bluetooth_icon.set_markup(
|
||||
icons.bluetooth if self.bluetooth_client.enabled else icons.bluetooth_off
|
||||
)
|
||||
|
||||
def update_network_status(self, *_):
|
||||
primary_device = self.network_client.primary_device
|
||||
|
||||
if primary_device == "wifi" and self.network_client.wifi_device:
|
||||
wifi_device = self.network_client.wifi_device
|
||||
self.set_tooltip_text(self.network_client.wifi_device.ssid)
|
||||
|
||||
if wifi_device.enabled:
|
||||
wifi_strength = wifi_device.strength
|
||||
|
||||
if wifi_strength == -1: # No active connection
|
||||
icon_label = icons.wifi_off
|
||||
else:
|
||||
icon_label = {
|
||||
80: icons.wifi,
|
||||
60: icons.good_signal,
|
||||
40: icons.moderate_signal,
|
||||
20: icons.weak_signal,
|
||||
0: icons.no_signal,
|
||||
}.get(min(80, 20 * round(wifi_strength / 20)), icons.wifi_off)
|
||||
else:
|
||||
icon_label = icons.wifi_off
|
||||
|
||||
elif primary_device == "wired":
|
||||
icon_label = icons.lan
|
||||
else:
|
||||
icon_label = icons.wifi_off
|
||||
|
||||
self.wifi_icon.set_markup(icon_label)
|
||||
@@ -1,33 +0,0 @@
|
||||
from fabric.audio.service import Audio
|
||||
from fabric.widgets.box import Box
|
||||
from snippets import MaterialIcon
|
||||
|
||||
|
||||
class MicrophoneIndicator(Box):
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.audio_service = Audio()
|
||||
self.microphone_icon = self.create_microphone_icon()
|
||||
|
||||
self.audio_service.connect("microphone_changed", self.update_microphone_status)
|
||||
self.update_microphone_status()
|
||||
|
||||
self.children = (self.microphone_icon,)
|
||||
|
||||
def create_microphone_icon(self):
|
||||
return MaterialIcon("mic_off", size=16)
|
||||
|
||||
def update_microphone_status(self, *args):
|
||||
current_microphone = self.audio_service.microphone
|
||||
|
||||
if current_microphone:
|
||||
is_muted = current_microphone.muted
|
||||
icon_name = "mic_off" if is_muted else "mic"
|
||||
|
||||
self.microphone_icon.set_label(icon_name)
|
||||
self.microphone_icon.set_visible(is_muted)
|
||||
else:
|
||||
self.microphone_icon.set_visible(False)
|
||||
|
||||
return True
|
||||
@@ -1,84 +0,0 @@
|
||||
import subprocess
|
||||
|
||||
import psutil
|
||||
from fabric import Fabricator
|
||||
from fabric.widgets.box import Box
|
||||
from snippets import MaterialIcon
|
||||
|
||||
|
||||
class NetworkIndicator(Box):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.update_interval = 5000 # Increased interval to 5 seconds
|
||||
self.last_status = None
|
||||
self.wifi_strength_cache = None
|
||||
self.fabricator = Fabricator(
|
||||
interval=self.update_interval,
|
||||
poll_from=self.update_network_status,
|
||||
)
|
||||
|
||||
def update_network_status(self, *_):
|
||||
current_status = self.get_network_status()
|
||||
if current_status != self.last_status:
|
||||
self.update_icon(current_status)
|
||||
self.last_status = current_status
|
||||
return True
|
||||
|
||||
def update_icon(self, current_status):
|
||||
self.icon_label = MaterialIcon(current_status, size=16)
|
||||
self.children = (self.icon_label,)
|
||||
|
||||
def get_network_status(self):
|
||||
if self.is_ethernet_connected():
|
||||
return "lan"
|
||||
if self.is_wifi_connected():
|
||||
return self.get_wifi_strength_icon()
|
||||
return "signal_wifi_off"
|
||||
|
||||
def get_wifi_strength_icon(self):
|
||||
wifi_strength = self.check_wifi_strength()
|
||||
if wifi_strength is not None:
|
||||
self.wifi_strength_cache = wifi_strength # Cache the result
|
||||
else:
|
||||
wifi_strength = self.wifi_strength_cache # Use cached value if available
|
||||
|
||||
return self.wifi_signal_icon(wifi_strength)
|
||||
|
||||
def wifi_signal_icon(self, strength):
|
||||
if strength is None or strength <= -100:
|
||||
return "signal_wifi_0_bar"
|
||||
if strength > -50:
|
||||
return "signal_wifi_4_bar"
|
||||
if strength > -60:
|
||||
return "network_wifi_3_bar"
|
||||
if strength > -70:
|
||||
return "network_wifi_2_bar"
|
||||
return "network_wifi_1_bar"
|
||||
|
||||
def check_wifi_strength(self):
|
||||
try:
|
||||
result = subprocess.run(["iwconfig"], capture_output=True, text=True)
|
||||
for line in result.stdout.splitlines():
|
||||
if "Signal level" in line:
|
||||
signal_level = line.split("Signal level=")[1].split(" ")[0]
|
||||
return int(signal_level.strip().replace("dBm", ""))
|
||||
return None
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def is_wifi_connected(self):
|
||||
return any(
|
||||
self._is_interface_up(interface, ["wlan", "wifi"])
|
||||
for interface in psutil.net_if_addrs()
|
||||
)
|
||||
|
||||
def is_ethernet_connected(self):
|
||||
return any(
|
||||
self._is_interface_up(interface, ["enp", "eth"])
|
||||
for interface in psutil.net_if_addrs()
|
||||
)
|
||||
|
||||
def _is_interface_up(self, interface, keywords):
|
||||
if any(substring in interface.lower() for substring in keywords):
|
||||
return psutil.net_if_stats().get(interface, {}).isup
|
||||
return False
|
||||
@@ -1,88 +1,83 @@
|
||||
from time import sleep
|
||||
import psutil
|
||||
from fabric import Fabricator
|
||||
from fabric.widgets.box import Box
|
||||
from fabric.widgets.button import Button
|
||||
from fabric.widgets.circularprogressbar import CircularProgressBar
|
||||
from gi.repository import GLib
|
||||
|
||||
from fabric.widgets.label import Label
|
||||
from fabric.widgets.box import Box
|
||||
from fabric.widgets.overlay import Overlay
|
||||
from snippets import MaterialIcon
|
||||
from fabric.widgets.circularprogressbar import CircularProgressBar
|
||||
|
||||
import snippets.iconss as icons
|
||||
|
||||
|
||||
class SystemInfo(Button):
|
||||
ICONS = [
|
||||
("CPU", "memory"),
|
||||
("Swap", "swap_horiz"),
|
||||
("RAM", "memory_alt"),
|
||||
("Temp", "thermometer"),
|
||||
]
|
||||
class SystemInfo(Box):
|
||||
ICONS = {
|
||||
"CPU": icons.cpu,
|
||||
"RAM": icons.memory,
|
||||
"Swap": icons.swap,
|
||||
"Disk": icons.disk,
|
||||
"Temp": icons.temp,
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(child=self._initialize_content())
|
||||
|
||||
Fabricator(
|
||||
poll_from=self._refresh_system_info,
|
||||
stream=True,
|
||||
on_changed=lambda fab, data: self._update_ui(data),
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(
|
||||
name="system-info",
|
||||
spacing=8,
|
||||
h_align="center",
|
||||
v_align="fill",
|
||||
visible=True,
|
||||
all_visible=True,
|
||||
)
|
||||
|
||||
def _initialize_content(self):
|
||||
self.box = Box(orientation="h", name="tray")
|
||||
self.progress_bars = {}
|
||||
self.icons = {}
|
||||
self.progress_bars = {
|
||||
system: CircularProgressBar(
|
||||
name="stats-progress",
|
||||
line_style="round",
|
||||
line_width=3,
|
||||
size=28,
|
||||
start_at=0,
|
||||
end_at=0.5,
|
||||
start_angle=0,
|
||||
end_angle=180,
|
||||
)
|
||||
for system in self.ICONS
|
||||
}
|
||||
|
||||
for system, icon_name in self.ICONS:
|
||||
self._create_system_item(system, icon_name)
|
||||
for system, icon_name in self.ICONS.items():
|
||||
overlay = Overlay(
|
||||
child=self.progress_bars[system],
|
||||
overlays=[Label(name="stats-icons", markup=icon_name)],
|
||||
)
|
||||
self.add(overlay)
|
||||
|
||||
return self.box
|
||||
GLib.timeout_add_seconds(1, self._update_system_info)
|
||||
|
||||
def _create_system_item(self, system, icon_name):
|
||||
self.progress_bars[system] = self._create_progress_bar()
|
||||
self.icons[system] = self._create_icon(icon_name)
|
||||
def _update_system_info(self):
|
||||
ram_usage = psutil.virtual_memory().percent
|
||||
swap_usage = psutil.swap_memory().percent
|
||||
disk_usage = psutil.disk_usage("/").percent
|
||||
cpu = psutil.cpu_percent(interval=0)
|
||||
|
||||
overlay = Overlay(
|
||||
child=self.progress_bars[system], overlays=[self.icons[system]]
|
||||
)
|
||||
self.box.pack_start(overlay, False, False, 4)
|
||||
usages = {
|
||||
"CPU": cpu,
|
||||
"RAM": ram_usage,
|
||||
"Swap": swap_usage,
|
||||
"Disk": disk_usage,
|
||||
"Temp": self._get_device_temperature(),
|
||||
}
|
||||
|
||||
def _create_progress_bar(self):
|
||||
return CircularProgressBar(
|
||||
name="progress",
|
||||
line_style="round",
|
||||
line_width=3,
|
||||
size=28,
|
||||
start_at=0,
|
||||
end_at=0.5,
|
||||
# start_angle=180,
|
||||
end_angle=180,
|
||||
)
|
||||
for system, usage in usages.items():
|
||||
self.progress_bars[system].value = usage / 100.0
|
||||
self.progress_bars[system].set_tooltip_text(f"{system} {usage:.1f}%")
|
||||
|
||||
def _create_icon(self, icon_name):
|
||||
return MaterialIcon(icon_name, 16)
|
||||
|
||||
def _refresh_system_info(self, fab: Fabricator):
|
||||
while True:
|
||||
yield {
|
||||
"CPU": round(psutil.cpu_percent()),
|
||||
"RAM": round(psutil.virtual_memory().percent),
|
||||
"Swap": round(psutil.swap_memory().percent),
|
||||
"Temp": self._get_device_temperature(),
|
||||
}
|
||||
sleep(1)
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def _get_device_temperature():
|
||||
try:
|
||||
temps = psutil.sensors_temperatures()
|
||||
if "coretemp" in temps:
|
||||
return round(temps["coretemp"][0].current, 1)
|
||||
elif "cpu_thermal" in temps:
|
||||
return round(temps["cpu_thermal"][0].current, 1)
|
||||
return None
|
||||
for key in ("coretemp", "cpu_thermal"):
|
||||
if key in temps:
|
||||
return round(temps[key][0].current, 1)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def _update_ui(self, usages):
|
||||
for system, usage in usages.items():
|
||||
self.progress_bars[system].value = usage / 100
|
||||
self.progress_bars[system].set_tooltip_text(f"{system} {usage}%")
|
||||
pass
|
||||
return None
|
||||
@@ -20,7 +20,7 @@ class PagerClient(TypedDict):
|
||||
|
||||
|
||||
class TaskBar(Box):
|
||||
def __init__(self, icon_size: int = 20, **kwargs):
|
||||
def __init__(self, icon_size: int = 22, **kwargs):
|
||||
super().__init__(
|
||||
orientation="h",
|
||||
spacing=7,
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
import json
|
||||
from fabric.utils import exec_shell_command_async, get_relative_path, invoke_repeater
|
||||
from fabric.widgets.box import Box
|
||||
from fabric.widgets.centerbox import CenterBox
|
||||
from fabric.widgets.label import Label
|
||||
from fabric.widgets.stack import Stack
|
||||
import snippets.iconss as icons
|
||||
from loguru import logger
|
||||
from gi.repository import Gtk
|
||||
from fabric.widgets.button import Button
|
||||
|
||||
|
||||
class UpdatesWidget(Button):
|
||||
"""A widget to display the number of available updates."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
**kwargs,
|
||||
):
|
||||
# Initialize the button with specific name and style
|
||||
super().__init__(name="button-bar", **kwargs)
|
||||
|
||||
self.script_file = get_relative_path("../../../config/scripts/systemupdates.sh")
|
||||
|
||||
self.update_level_label = Label(
|
||||
name="update-label",
|
||||
label="0",
|
||||
visible=False, # Initially hidden
|
||||
)
|
||||
|
||||
self.update_icon = Label(name="update-icon", markup=icons.update)
|
||||
self.updated_icon = Label(name="updated-icon", markup=icons.updated)
|
||||
|
||||
self.update_box = CenterBox(
|
||||
center_children=[self.update_icon, self.update_level_label],
|
||||
)
|
||||
|
||||
self.updated_box = CenterBox(
|
||||
center_children=[self.updated_icon],
|
||||
)
|
||||
|
||||
self.stack = Stack()
|
||||
self.stack.add_named(self.update_box, "update_box")
|
||||
self.stack.add_named(self.updated_box, "updated_box")
|
||||
self.stack.set_visible_child_name("update_box")
|
||||
|
||||
self.children = Box(
|
||||
children=self.stack,
|
||||
)
|
||||
|
||||
# Ensure the stack resizes to the current visible child
|
||||
self.stack.set_homogeneous(False)
|
||||
self.stack.set_transition_type(Gtk.StackTransitionType.CROSSFADE)
|
||||
|
||||
# Connect the button press event
|
||||
self.connect("button-press-event", self.on_button_press)
|
||||
|
||||
# Set up a repeater to call the update method at specified intervals
|
||||
invoke_repeater(60000, self.update, initial_call=True)
|
||||
|
||||
def update_values(self, value: str):
|
||||
logger.info(f"[Updates] Received update JSON: {value}")
|
||||
|
||||
try:
|
||||
value = json.loads(value)
|
||||
except json.JSONDecodeError:
|
||||
logger.error("[Updates] Failed to parse update JSON")
|
||||
return False
|
||||
|
||||
update_count = str(value["total"])
|
||||
self.update_level_label.set_label(update_count)
|
||||
|
||||
if update_count == "0":
|
||||
self.stack.set_visible_child_name("updated_box")
|
||||
self.update_level_label.set_visible(False)
|
||||
else:
|
||||
self.stack.set_visible_child_name("update_box")
|
||||
self.update_level_label.set_visible(True)
|
||||
|
||||
self.set_tooltip_text(value.get("tooltip", ""))
|
||||
return True
|
||||
|
||||
def on_button_press(self, _, event):
|
||||
if event.button == 1:
|
||||
exec_shell_command_async(
|
||||
f"{self.script_file} -arch -up",
|
||||
lambda _: None,
|
||||
)
|
||||
self.update()
|
||||
return True
|
||||
else:
|
||||
self.update()
|
||||
|
||||
def update(self):
|
||||
logger.info("[Updates] Checking for updates...")
|
||||
|
||||
# Execute the update script asynchronously and update values
|
||||
exec_shell_command_async(
|
||||
f"{self.script_file} -arch",
|
||||
lambda output: self.update_values(output),
|
||||
)
|
||||
|
||||
return True
|
||||
@@ -1,81 +0,0 @@
|
||||
from fabric import Fabricator
|
||||
from fabric.utils import exec_shell_command
|
||||
from fabric.widgets.box import Box
|
||||
from fabric.widgets.button import Button
|
||||
from snippets import MaterialIcon
|
||||
from services import audio
|
||||
|
||||
VOLUME_ICONS = {
|
||||
"muted": "volume_off",
|
||||
"low": "volume_mute",
|
||||
"medium-low": "volume_down",
|
||||
"medium-high": "volume_up",
|
||||
"high": "volume_up",
|
||||
}
|
||||
|
||||
|
||||
class VolumeIndicator(Box):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.audio_service = audio
|
||||
self.volume_icon_button = self.create_volume_icon()
|
||||
|
||||
self.volume_icon_button.connect("button-press-event", self.on_button_press)
|
||||
self.audio_service.connect("changed", self.update_volume_status)
|
||||
|
||||
Fabricator(interval=1000, poll_from=self.update_volume_status)
|
||||
self.children = (self.volume_icon_button,)
|
||||
|
||||
def create_volume_icon(self):
|
||||
icon = MaterialIcon(VOLUME_ICONS["muted"], size=16)
|
||||
button = Button(child=icon)
|
||||
button.icon_widget = icon
|
||||
return button
|
||||
|
||||
def on_button_press(self, widget, event):
|
||||
if event.button == 1:
|
||||
self.toggle_mute()
|
||||
elif event.button == 3:
|
||||
self.open_audio_settings()
|
||||
|
||||
def toggle_mute(self):
|
||||
current_stream = self.audio_service.speaker
|
||||
if current_stream:
|
||||
current_stream.muted = not current_stream.muted
|
||||
# self.update_volume_status()
|
||||
|
||||
def open_audio_settings(self):
|
||||
exec_shell_command("pavucontrol")
|
||||
|
||||
def update_volume_status(self, *_):
|
||||
current_stream = self.audio_service.speaker
|
||||
if current_stream:
|
||||
self.update_icon(current_stream)
|
||||
|
||||
return True
|
||||
|
||||
def update_icon(self, stream):
|
||||
if stream.muted:
|
||||
icon_name = VOLUME_ICONS["muted"]
|
||||
volume_percentage = 0
|
||||
tooltip_text = "Volume Muted"
|
||||
else:
|
||||
volume_level = stream.volume
|
||||
icon_name = self.get_volume_icon(volume_level)
|
||||
volume_percentage = int(volume_level)
|
||||
tooltip_text = f"Volume: {volume_percentage}%"
|
||||
|
||||
self.volume_icon_button.icon_widget.set_label(icon_name)
|
||||
|
||||
self.volume_icon_button.set_tooltip_text(tooltip_text)
|
||||
|
||||
def get_volume_icon(self, volume_level):
|
||||
if volume_level == 0:
|
||||
return VOLUME_ICONS["low"]
|
||||
elif volume_level <= 33:
|
||||
return VOLUME_ICONS["medium-low"]
|
||||
elif volume_level <= 66:
|
||||
return VOLUME_ICONS["medium-high"]
|
||||
else:
|
||||
return VOLUME_ICONS["high"]
|
||||
Reference in new issue
Block a user