diff --git a/config/fabric/icon.py b/config/fabric/icon.py new file mode 100644 index 00000000..881cf7a1 --- /dev/null +++ b/config/fabric/icon.py @@ -0,0 +1,18 @@ +# material_icon.py + +from fabric.widgets.label import Label + +def MaterialIcon(icon_name: str, size: str = "24px", props: dict = None): + label_props = { + "label": icon_name, # Directly use the provided icon name + "name": "material_icon", + "style": f"font-size: {size}; ", # Set font family for Material Icons + "h_align": "center", # Align horizontally + "v_align": "center" # Align vertically + } + + if props: + label_props.update(props) + + return Label(**label_props) + diff --git a/config/fabric/modules/bar/bar.py b/config/fabric/modules/bar/bar.py index 34b27e43..80401209 100644 --- a/config/fabric/modules/bar/bar.py +++ b/config/fabric/modules/bar/bar.py @@ -1,21 +1,23 @@ -from fabric.hyprland.widgets import Language, WorkspaceButton, Workspaces +from fabric.hyprland.widgets import Language from fabric.system_tray.widgets import SystemTray -from fabric.utils import FormattedString, bulk_replace +from fabric.utils import FormattedString, bulk_replace, exec_shell_command from fabric.widgets.box import Box +from fabric.widgets.button import Button from fabric.widgets.centerbox import CenterBox from fabric.widgets.datetime import DateTime from fabric.widgets.wayland import WaylandWindow as Window from modules.bar.widgets import ( BatteryLabel, Bluetooth, + HardwareUsage, MicrophoneIndicator, + Network, PowerProfile, TaskBar, VolumeIndicator, - Wifi, + workspace, ) - -from .glance import OpenAppsBar +from icon import MaterialIcon class Bar(Window): @@ -29,11 +31,6 @@ class Bar(Window): visible=False, all_visible=False, ) - self.workspaces = Workspaces( - name="workspaces", - spacing=4, - buttons=[WorkspaceButton(id=i, label=str(i)) for i in range(1, 11)], - ) self.language = Language( formatter=FormattedString( "{replace_lang(language)}", @@ -46,17 +43,28 @@ class Bar(Window): ), ) self.date_time = DateTime(formatters=["%-I:%M 󰧞 %a %d %b"], name="datetime") - self.system_tray = SystemTray(spacing=4, icon_size=18) + self.system_tray = SystemTray(name="tray", spacing=4, icon_size=18) self.TaskBar = TaskBar(icon_size=18) self.volume = VolumeIndicator() - self.wifi = Wifi() + self.network = Network() self.bluetooth = Bluetooth() self.battery = BatteryLabel(name="battery") self.microphone = MicrophoneIndicator() self.power = PowerProfile() - self.open_apps_bar = OpenAppsBar() - + self.workspaces = Button( + child=workspace, + name="workspaces", + on_clicked=lambda _: exec_shell_command("wofi --show drun --fork"), + ) + self.logo = Button( + child=MaterialIcon("search", size="16px"), + name="logo", + on_clicked=lambda _: exec_shell_command( + "/home/sankalp/fabric/fabric_send 'launcher.toggle()'" + ), + ) + self.info = HardwareUsage() self.applets = Box( name="applets", spacing=4, @@ -64,10 +72,9 @@ class Bar(Window): self.language, self.power, self.bluetooth, - self.wifi, + self.network, self.volume, self.microphone, - # self.battery, ], ) self.children = CenterBox( @@ -76,14 +83,14 @@ class Bar(Window): name="start-container", spacing=8, orientation="h", - children=self.TaskBar, + children=[self.logo, self.workspaces, self.info], ), center_children=Box( name="center-container", spacing=8, orientation="h", children=[ - self.workspaces, + self.TaskBar, ], ), end_children=Box( diff --git a/config/fabric/modules/bar/glance.py b/config/fabric/modules/bar/glance.py deleted file mode 100644 index be006b70..00000000 --- a/config/fabric/modules/bar/glance.py +++ /dev/null @@ -1,54 +0,0 @@ -import gi -from fabric.widgets.box import Box -from fabric.widgets.button import Button -from fabric.widgets.image import Image - -from .icon_resolver import IconResolver - -gi.require_version("Glace", "0.1") -from gi.repository import Glace - - -class OpenAppsBar(Box): - def __init__(self): - self.client_buttons = {} - super().__init__(spacing=10) - self.icon_resolver = IconResolver() - self._manager = Glace.Manager() - self._manager.connect("client-added", self.on_client_added) - - def on_active_window(self, _, event): - print(event.data) - print(self.client_buttons) - # client_buttons[] - - def on_client_added(self, _, client: Glace.Client): - client_image = Image() - client_button = Button( - name="panel-button", - image=client_image, - on_button_press_event=lambda *_: client.activate(), - ) - self.client_buttons[client.get_id()] = client_button - - client.connect( - "notify::app-id", - lambda *_: [ - client_image.set_from_pixbuf( - self.icon_resolver.get_icon_pixbuf(client.get_app_id(), 24) - ), - print(client.get_id()), - ], - ) - - client.connect( - "notify::activated", - lambda *_: ( - client_button.add_style_class("activated") - if client.get_activated() - else client_button.remove_style_class("activated") - ), - ) - client.bind_property("title", client_button, "tooltip-text", 0) - client.connect("close", lambda *_: self.remove(client_button)) - self.add(client_button) diff --git a/config/fabric/modules/bar/icon_resolver.py b/config/fabric/modules/bar/icon_resolver.py deleted file mode 100644 index 883ed1a1..00000000 --- a/config/fabric/modules/bar/icon_resolver.py +++ /dev/null @@ -1,100 +0,0 @@ -import json -import os -import re - -import gi - -gi.require_version("Gtk", "3.0") -from gi.repository import GLib, Gtk -from loguru import logger - -# TODO WIP -# Idea: nearest string matching algorithm -# if already exists: retrieve from json -# if found: stor in json: app_id -> icon_name -# if not found: store in json -> misisng-icon - - -CACHE_DIR = str(GLib.get_user_cache_dir()) + "/fabric" -ICON_CACHE_FILE = CACHE_DIR + "/icons.json" -if not os.path.exists(CACHE_DIR): - os.makedirs(CACHE_DIR) - - -class IconResolver: - def __init__(self, default_applicaiton_icon: str = "application-x-symbolic"): - if os.path.exists(ICON_CACHE_FILE): - f = open(ICON_CACHE_FILE) - try: - self._icon_dict = json.load(f) - except json.JSONDecodeError: - logger.info("[ICONS] Cache file does not exist or is corrupted") - f.close() - else: - self._icon_dict = {} - - self.default_applicaiton_icon = default_applicaiton_icon - - def get_icon_name(self, app_id: str): - if app_id in self._icon_dict: - return self._icon_dict[app_id] - new_icon = self._compositor_find_icon(app_id) - logger.info( - f"[ICONS] found new icon: '{new_icon}' for app id: '{app_id}', storing..." - ) - self._store_new_icon(app_id, new_icon) - return new_icon - - def get_icon_pixbuf(self, app_id: str, size: int = 16): - return Gtk.IconTheme.get_default().load_icon( - self.get_icon_name(app_id), - size, - Gtk.IconLookupFlags.FORCE_SIZE, - ) - - def _store_new_icon(self, app_id: str, icon: str): - self._icon_dict[app_id] = icon - with open(ICON_CACHE_FILE, "w") as f: - json.dump(self._icon_dict, f) - f.close() - - def _get_icon_from_desktop_file(self, desktop_file_path: str): - # TODO: get icon in the [Desktop Entry] section only - with open(desktop_file_path) as f: - for line in f.readlines(): - if "Icon=" in line: - return "".join(line[5:].split()) - return self.default_applicaiton_icon - - def _get_desktop_file(self, app_id: str) -> str | None: - data_dirs = GLib.get_system_data_dirs() - for data_dir in data_dirs: - data_dir = data_dir + "/applications/" - if os.path.exists(data_dir): - # Do name resolving here - - files = os.listdir(data_dir) - matching = [ - s for s in files if "".join(app_id.lower().split()) in s.lower() - ] - if matching: - return data_dir + matching[0] - - for word in list(filter(None, re.split(r"-|\.|_|\s", app_id))): - matching = [s for s in files if word.lower() in s.lower()] - if matching: - return data_dir + matching[0] - - return None - - def _compositor_find_icon(self, app_id: str): - if Gtk.IconTheme.get_default().has_icon(app_id): - return app_id - if Gtk.IconTheme.get_default().has_icon(app_id + "-desktop"): - return app_id + "-desktop" - desktop_file = self._get_desktop_file(app_id) - return ( - self._get_icon_from_desktop_file(desktop_file) - if desktop_file - else self.default_applicaiton_icon - ) diff --git a/config/fabric/modules/bar/widgets/__init__.py b/config/fabric/modules/bar/widgets/__init__.py index b5bc916d..61b7a0e2 100644 --- a/config/fabric/modules/bar/widgets/__init__.py +++ b/config/fabric/modules/bar/widgets/__init__.py @@ -1,7 +1,9 @@ from .battery import * from .bluetooth import * +from .info import * from .microphone import * +from .network import * from .powerprofile import * from .taskbar import * from .volume import * -from .wifi import * +from .workspace import * diff --git a/config/fabric/modules/bar/widgets/__pycache__/__init__.cpython-312.pyc b/config/fabric/modules/bar/widgets/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..b265d066 Binary files /dev/null and b/config/fabric/modules/bar/widgets/__pycache__/__init__.cpython-312.pyc differ diff --git a/config/fabric/modules/bar/widgets/__pycache__/battery.cpython-312.pyc b/config/fabric/modules/bar/widgets/__pycache__/battery.cpython-312.pyc new file mode 100644 index 00000000..c70ecfb8 Binary files /dev/null and b/config/fabric/modules/bar/widgets/__pycache__/battery.cpython-312.pyc differ diff --git a/config/fabric/modules/bar/widgets/__pycache__/bluetooth.cpython-312.pyc b/config/fabric/modules/bar/widgets/__pycache__/bluetooth.cpython-312.pyc new file mode 100644 index 00000000..3415dd72 Binary files /dev/null and b/config/fabric/modules/bar/widgets/__pycache__/bluetooth.cpython-312.pyc differ diff --git a/config/fabric/modules/bar/widgets/__pycache__/idle.cpython-312.pyc b/config/fabric/modules/bar/widgets/__pycache__/idle.cpython-312.pyc new file mode 100644 index 00000000..a1a4dec2 Binary files /dev/null and b/config/fabric/modules/bar/widgets/__pycache__/idle.cpython-312.pyc differ diff --git a/config/fabric/modules/bar/widgets/__pycache__/info.cpython-312.pyc b/config/fabric/modules/bar/widgets/__pycache__/info.cpython-312.pyc new file mode 100644 index 00000000..9092e104 Binary files /dev/null and b/config/fabric/modules/bar/widgets/__pycache__/info.cpython-312.pyc differ diff --git a/config/fabric/modules/bar/widgets/__pycache__/material_icon.cpython-312.pyc b/config/fabric/modules/bar/widgets/__pycache__/material_icon.cpython-312.pyc new file mode 100644 index 00000000..3a80706c Binary files /dev/null and b/config/fabric/modules/bar/widgets/__pycache__/material_icon.cpython-312.pyc differ diff --git a/config/fabric/modules/bar/widgets/__pycache__/microphone.cpython-312.pyc b/config/fabric/modules/bar/widgets/__pycache__/microphone.cpython-312.pyc new file mode 100644 index 00000000..35203a9f Binary files /dev/null and b/config/fabric/modules/bar/widgets/__pycache__/microphone.cpython-312.pyc differ diff --git a/config/fabric/modules/bar/widgets/__pycache__/network.cpython-312.pyc b/config/fabric/modules/bar/widgets/__pycache__/network.cpython-312.pyc new file mode 100644 index 00000000..a45969f9 Binary files /dev/null and b/config/fabric/modules/bar/widgets/__pycache__/network.cpython-312.pyc differ diff --git a/config/fabric/modules/bar/widgets/__pycache__/powerprofile.cpython-312.pyc b/config/fabric/modules/bar/widgets/__pycache__/powerprofile.cpython-312.pyc new file mode 100644 index 00000000..20218d07 Binary files /dev/null and b/config/fabric/modules/bar/widgets/__pycache__/powerprofile.cpython-312.pyc differ diff --git a/config/fabric/modules/bar/widgets/__pycache__/taskbar.cpython-312.pyc b/config/fabric/modules/bar/widgets/__pycache__/taskbar.cpython-312.pyc new file mode 100644 index 00000000..9dfe4c69 Binary files /dev/null and b/config/fabric/modules/bar/widgets/__pycache__/taskbar.cpython-312.pyc differ diff --git a/config/fabric/modules/bar/widgets/__pycache__/volume.cpython-312.pyc b/config/fabric/modules/bar/widgets/__pycache__/volume.cpython-312.pyc new file mode 100644 index 00000000..5040191c Binary files /dev/null and b/config/fabric/modules/bar/widgets/__pycache__/volume.cpython-312.pyc differ diff --git a/config/fabric/modules/bar/widgets/__pycache__/wifi.cpython-312.pyc b/config/fabric/modules/bar/widgets/__pycache__/wifi.cpython-312.pyc new file mode 100644 index 00000000..96135b1c Binary files /dev/null and b/config/fabric/modules/bar/widgets/__pycache__/wifi.cpython-312.pyc differ diff --git a/config/fabric/modules/bar/widgets/__pycache__/workspace.cpython-312.pyc b/config/fabric/modules/bar/widgets/__pycache__/workspace.cpython-312.pyc new file mode 100644 index 00000000..ff2a1286 Binary files /dev/null and b/config/fabric/modules/bar/widgets/__pycache__/workspace.cpython-312.pyc differ diff --git a/config/fabric/modules/bar/widgets/battery.py b/config/fabric/modules/bar/widgets/battery.py index 1bc0d2ce..6c9d116c 100644 --- a/config/fabric/modules/bar/widgets/battery.py +++ b/config/fabric/modules/bar/widgets/battery.py @@ -2,25 +2,24 @@ import psutil from fabric.utils import invoke_repeater from fabric.widgets.box import Box from fabric.widgets.label import Label - +from icon import MaterialIcon 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_alret", "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) - self.battery_icon = Label( - label="󰁹", - name="symbol", - # style="font-size: 16px; margin-right: 4px;", - ) - self.battery_label = Label( - label="N/A", - style="font-size: 16px; margin-left: 4px;", - ) - invoke_repeater(1000, self.update_battery_status, initial_call=True) - self.children = self.battery_icon, self.battery_label - def update_battery_status(self): battery = psutil.sensors_battery() if battery is None: @@ -28,22 +27,17 @@ class BatteryLabel(Box): return battery_percent = round(battery.percent) if battery else 0.0 - self.battery_label.set_label(f"{(battery_percent or 'N/A')}%") + battery_label = Label(label=f"{battery_percent}%") is_charging = battery.power_plugged if battery else False + icons = self.ICONS_CHARGING if is_charging else self.ICONS_NOT_CHARGING - icons = ( - ["󰢟", "󰂆", "󰂇", "󰂈", "󰢝", "󰂉", "󰢞", "󰂊", "󰂋", "󰂅"] - if is_charging - else ["󱃍", "󰁻", "󰁼", "󰁽", "󰁾", "󰁿", "󰂀", "󰂁", "󰂂", "󰁹"] - ) - index = min(max(battery_percent // 10, 0), 9) + index = min(max(battery_percent // 10, 0), 10) + battery_icon = MaterialIcon(icons[index], size="16px") - self.battery_icon.set_label(icons[index]) + self.children = (battery_icon, battery_label) - if battery_percent == 100: - self.hide() - else: - self.show() + self.show() if battery_percent < 100 else self.hide() return True + diff --git a/config/fabric/modules/bar/widgets/bluetooth.py b/config/fabric/modules/bar/widgets/bluetooth.py index ba8c2566..dba05448 100644 --- a/config/fabric/modules/bar/widgets/bluetooth.py +++ b/config/fabric/modules/bar/widgets/bluetooth.py @@ -1,28 +1,35 @@ from fabric.bluetooth import BluetoothClient from fabric.widgets.box import Box -from fabric.widgets.label import Label - +from icon import MaterialIcon # Import your MaterialIcon function class Bluetooth(Box): + ICON_ENABLED = "bluetooth" # Use the appropriate Material icon name + ICON_DISABLED = "bluetooth_disabled" # Use the appropriate Material icon name + def __init__(self, **kwargs): super().__init__(**kwargs) - self.bluetooth_client = BluetoothClient() - self.bluetooth_icon = Label( - label="󰂲", - # name="symbol", - style="font-size: 18px;", - ) + + self.bluetooth_icon = None self.bluetooth_client.connect("changed", self.update_bluetooth_status) + self.update_bluetooth_status() - self.add(self.bluetooth_icon) def update_bluetooth_status(self, *args): - if self.bluetooth_client.enabled: - self.bluetooth_icon.set_label("󰂯") - else: - self.bluetooth_icon.set_label("󰂲") + icon_label = ( + self.ICON_ENABLED if self.bluetooth_client.enabled else self.ICON_DISABLED + ) + + new_icon = MaterialIcon(icon_label, size="16px") + + if self.bluetooth_icon: + self.remove(self.bluetooth_icon) + + self.bluetooth_icon = new_icon + self.add(self.bluetooth_icon) def on_destroy(self): + """Disconnect the Bluetooth client when the widget is destroyed.""" self.bluetooth_client.disconnect("changed", self.update_bluetooth_status) + diff --git a/config/fabric/modules/bar/widgets/idle.py b/config/fabric/modules/bar/widgets/idle.py deleted file mode 100644 index f55f0065..00000000 --- a/config/fabric/modules/bar/widgets/idle.py +++ /dev/null @@ -1,35 +0,0 @@ -from fabric.widgets.box import Box -from fabric.widgets.label import Label -from services.inhibit import Inhibit # Importing Inhibit from the services module - - -class IdleIndicator(Box): - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.inhibit_service = Inhibit() # Use the imported Inhibit service - self.idle_icon = Label( - label="󰅐", # Icon symbol for idle/inhibit indication - name="symbol", - ) - - # Add the idle icon as a child widget - self.children = (self.idle_icon,) - - # Check the initial inhibition state and update the icon visibility - self.update_idle_status() - - def update_idle_status(self): - """Update the idle status based on inhibition state.""" - print( - f"Inhibition state: {self.inhibit_service.is_inhibit}" - ) # Debugging output - - if self.inhibit_service.is_inhibit: - self.idle_icon.set_visible(True) # Show the idle icon when inhibited - else: - self.idle_icon.set_visible(False) # Hide the idle icon when not inhibited - - def toggle_idle(self): - """Toggle the idle state by interacting with the inhibit service.""" - self.inhibit_service.toggle() # Toggle the inhibition state - self.update_idle_status() # Refresh the icon visibility diff --git a/config/fabric/modules/bar/widgets/info.py b/config/fabric/modules/bar/widgets/info.py new file mode 100644 index 00000000..398e9493 --- /dev/null +++ b/config/fabric/modules/bar/widgets/info.py @@ -0,0 +1,59 @@ +import psutil +from fabric.utils import invoke_repeater +from fabric.widgets.box import Box +from fabric.widgets.button import Button +from fabric.widgets.label import Label +from fabric.widgets.overlay import Overlay +from fabric.widgets.circularprogressbar import CircularProgressBar +from icon import MaterialIcon # Import your updated MaterialIcon function + +class HardwareUsage(Button): + HARDWARE_ITEMS = [ + ("CPU", "settings_motion_mode"), # Pass the Material icon name directly + ("Swap", "swap_horiz"), + ("RAM", "memory"), + ] + + def __init__(self): + super().__init__(child=self._create_content()) + invoke_repeater(1000, self._update_labels) + + def _create_content(self): + self.box = Box(orientation="h", name="tray") + self.progress_bars = {} + self.labels = {} + self.icons = {} + + for hardware, icon_name in self.HARDWARE_ITEMS: + self.progress_bars[hardware] = self._create_progress_bar() + self.labels[hardware] = Label(style="font-size:14px; margin: 4px;") + self.icons[hardware] = MaterialIcon(icon_name, size="16px") + self._pack_items(hardware) + + return self.box + + def _create_progress_bar(self): + return CircularProgressBar(name="progress", pie=True, size=30) + + def _pack_items(self, hardware): + overlay = Overlay(child=self.progress_bars[hardware], overlays=self.icons[hardware]) + self.box.pack_start(overlay, False, False, 0) + self.box.pack_start(self.labels[hardware], False, False, 0) + + def _update_labels(self): + usages = self._get_hardware_usages() + self._update_progress_bars(usages) + + def _get_hardware_usages(self): + return { + "CPU": round(psutil.cpu_percent()), + "RAM": round(psutil.virtual_memory().percent), + "Swap": round(psutil.swap_memory().percent), + } + + def _update_progress_bars(self, usages): + for hardware, usage in usages.items(): + self.progress_bars[hardware].value = usage / 100 + self.labels[hardware].set_label(f"{usage}%") + self.progress_bars[hardware].set_tooltip_text(f"{hardware} Usage: {usage}%") + diff --git a/config/fabric/modules/bar/widgets/microphone.py b/config/fabric/modules/bar/widgets/microphone.py index 97835264..186e36d4 100644 --- a/config/fabric/modules/bar/widgets/microphone.py +++ b/config/fabric/modules/bar/widgets/microphone.py @@ -1,33 +1,35 @@ from fabric.audio.service import Audio from fabric.widgets.box import Box -from fabric.widgets.label import Label - +from icon import MaterialIcon # Import your MaterialIcon function class MicrophoneIndicator(Box): - def __init__(self, **kwargs): + ICON_MUTED = "mic_off" + ICON_UNMUTED = "mic" + + def __init__(self, **kwargs): super().__init__(**kwargs) - audio_service = Audio() - self.audio_service = audio_service - self.microphone_icon = Label( - label="󰍭", - name="symbol", - # style="font-size: 16px; margin-left:4px;", - ) + 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(self.ICON_MUTED, size="16px") + def update_microphone_status(self, *args): current_microphone = self.audio_service.microphone + if current_microphone: is_muted = current_microphone.muted + icon_name = self.ICON_MUTED if is_muted else self.ICON_UNMUTED - if is_muted: - self.microphone_icon.set_visible(True) - else: - self.microphone_icon.set_visible(False) + self.microphone_icon.set_label(icon_name) + self.microphone_icon.set_visible(is_muted) + else: + self.microphone_icon.set_visible(False) return True + diff --git a/config/fabric/modules/bar/widgets/network.py b/config/fabric/modules/bar/widgets/network.py new file mode 100644 index 00000000..b4e64c47 --- /dev/null +++ b/config/fabric/modules/bar/widgets/network.py @@ -0,0 +1,69 @@ +import psutil +from fabric.utils import invoke_repeater +from fabric.widgets.box import Box +from icon import MaterialIcon +import subprocess + +class Network(Box): + def __init__(self, **kwargs): + super().__init__(**kwargs) + self.update_interval = 5000 + self.last_status = None + invoke_repeater(self.update_interval, self.update_network_status, initial_call=True) + + 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="16px") + self.children = (self.icon_label,) + + def get_network_status(self): + if self.is_ethernet_connected(): + return "network_ethernet" + 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() + + 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, ["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 + diff --git a/config/fabric/modules/bar/widgets/powerprofile.py b/config/fabric/modules/bar/widgets/powerprofile.py index 30dfe4fa..53da53ac 100644 --- a/config/fabric/modules/bar/widgets/powerprofile.py +++ b/config/fabric/modules/bar/widgets/powerprofile.py @@ -1,27 +1,21 @@ from fabric.utils import exec_shell_command, invoke_repeater from fabric.widgets.box import Box -from fabric.widgets.label import Label +from icon import MaterialIcon PROFILE_ICONS = { - "power-saver": "󰡳", + "power-saver": "data_saver_on", "balanced": None, - "performance": "󰡴", + "performance": "mode_heat", } - class PowerProfile(Box): def __init__(self, **kwargs): super().__init__(**kwargs) - self.profile_icon = Label( - label="󰡳", - name="symbol", - # style="font-size: 16px; margin-right: 4px;", - ) + + self.profile_icon = None invoke_repeater(1000, self.update_power_profile, initial_call=True) - self.children = (self.profile_icon,) - def update_power_profile(self): profile = self.get_power_profile() self.update_icon(profile) @@ -29,12 +23,22 @@ class PowerProfile(Box): def get_power_profile(self): result = exec_shell_command("powerprofilesctl get") - return result.strip() if result else "power-saver" + return result.strip() or "power-saver" def update_icon(self, profile): - if profile in ["power-saver", "performance"]: - icon = PROFILE_ICONS[profile] - self.profile_icon.set_label(icon) - self.profile_icon.show() + icon_name = PROFILE_ICONS.get(profile) + + new_icon = MaterialIcon(icon_name, size="16px") if icon_name else None + + if self.profile_icon: + self.remove(self.profile_icon) + + if new_icon: + self.profile_icon = new_icon + self.add(self.profile_icon) else: - self.profile_icon.hide() + if self.profile_icon: + self.profile_icon.hide() + self.profile_icon = None + + diff --git a/config/fabric/modules/bar/widgets/taskbar11.py b/config/fabric/modules/bar/widgets/taskbar11.py deleted file mode 100644 index 3e176ee7..00000000 --- a/config/fabric/modules/bar/widgets/taskbar11.py +++ /dev/null @@ -1,180 +0,0 @@ -import glob -import json -import subprocess -from typing import TypedDict - -from fabric.hyprland.widgets import get_hyprland_connection -from fabric.widgets.box import Box -from fabric.widgets.button import Button -from fabric.widgets.image import Image -from fabric.widgets.label import Label -from gi.repository import GdkPixbuf, GLib, Gtk - - -class PagerClient(TypedDict): - title: str - initialClass: str - mapped: bool - hidden: bool - address: str - - -class TaskBar(Box): - def __init__(self, icon_size: int = 24, **kwargs): - super().__init__(orientation="h", spacing=6, name="taskbar", **kwargs) - self.connection = get_hyprland_connection() - self.icon_size = icon_size - self.icon_theme = Gtk.IconTheme.get_default() - - self.set_visible(False) - - if self.connection.ready: - self.render_with_delay() - else: - self.connection.connect("event::ready", lambda *_: self.render_with_delay()) - - for event in ( - "activewindow", - "openwindow", - "closewindow", - "changefloatingmode", - ): - self.connection.connect("event::" + event, lambda *_: self.render(None)) - - def render_with_delay(self, *_): - GLib.timeout_add(100, self.render) - - def render(self, *_): - self.children = [] - - clients = self.fetch_clients() - active_window_address = self.get_active_window_address() - - # Group clients by initialClass - grouped_clients = {} - for client in clients: - if client["mapped"] and not client["hidden"]: - window_class = client["initialClass"].lower() - if window_class not in grouped_clients: - grouped_clients[window_class] = [] - grouped_clients[window_class].append(client) - - if grouped_clients: - for window_class, client_group in grouped_clients.items(): - # Fetch the icon for the application (one icon per group) - icon = self.bake_window_icon(window_class) - - # Create a vertical box inside the button for the icon and the dots - icon_box = Box(orientation="v", spacing=0) - icon_box.set_halign(Gtk.Align.CENTER) - - # Add the icon at the top - icon_box.add(icon) - - # Add dot(s) directly below the icon to indicate the number of open instances - if len(client_group) > 1: - dots_box = Box(orientation="h", spacing=2) # Box to hold the dots - dots_box.set_halign(Gtk.Align.CENTER) - - for _ in range(len(client_group)): - dot = Label( - label="•" - ) # Each dot represents one window instance - dot.set_style( - "font-size: 7px; color: rgba(255, 255, 255, 0.6);" - ) # Lighter dot color - dots_box.add(dot) - - icon_box.add(dots_box) - - # Create a button and add the icon_box (icon + dots) inside the button - button = Button(child=icon_box, tooltip_text=client_group[0]["title"]) - - # Highlight the active window - if any( - client["address"] == active_window_address - for client in client_group - ): - button.set_style( - "background-color: @surfaceVariant; transition-duration: 0.3s; border-radius:100px;" - ) - - # Connect click event to focus the application window - button.connect( - "button-press-event", self.on_icon_click, client_group[0]["address"] - ) - - # Add the button to the taskbar - self.add(button) - - self.set_visible(True) - self.show_all() - else: - self.set_visible(False) - - def get_active_window_address(self) -> str: - # Fetch the address of the currently active window - active_window_info = json.loads( - self.connection.send_command("j/activewindow").reply.decode() - ) - print(f"Active window address: {active_window_info.get('address', '')}") - return active_window_info.get("address", "") - - def on_icon_click(self, widget, event, address): - command = ["hyprctl", "dispatch", "focuswindow", f"address:{address}"] - subprocess.run(command, check=True) - - def fetch_clients(self) -> list[PagerClient]: - return json.loads(self.connection.send_command("j/clients").reply.decode()) - - def bake_window_icon( - self, window_class: str, fallback_icon: str = "image-missing" - ) -> Image: - icon_name = self.get_icon_from_desktop_entry(window_class) - - if icon_name: - pixbuf = self.load_icon(icon_name) - else: - pixbuf = self.load_icon(window_class, fallback_icon) - - return Image(pixbuf=pixbuf, size=self.icon_size) - - def get_icon_from_desktop_entry(self, window_class: str) -> str: - desktop_files = glob.glob("/usr/share/applications/*.desktop") - for desktop_file in desktop_files: - with open(desktop_file, "r") as f: - for line in f: - if line.startswith("Name="): - app_name = line.split("=", 1)[1].strip().lower() - if window_class in app_name: - for line in f: - if line.startswith("Icon="): - return line.split("=", 1)[1].strip() - return None - - def load_icon( - self, icon_name: str, fallback_icon: str = "image-missing" - ) -> GdkPixbuf.Pixbuf: - try: - pixbuf = self.icon_theme.load_icon( - icon_name,ecode - raise JSONDecodeError("Expecting value", s, err.value) from None -json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0) -Active window address: 0x60d8e93ecf50 -Active window address: 0x60d8e93ecf50 -Active window address: 0x60d8e93ecf50 -^C2024-10-22 13:47:53.414 | INFO | fabric.core.application:on_exit:316 - [Fabric] exiting... - -sankalp on 53ur!tyD3m0n fabric/rework/bar via 🐍 v3.12.7 [⏱ 1m4s]✔ -╰─λ - self.icon_size, - Gtk.IconLookupFlags.FORCE_SIZE, - ) - except Exception: - pixbuf = self.icon_theme.load_icon( - fallback_icon, - self.icon_size, - Gtk.IconLookupFlags.FORCE_SIZE, - ) - return pixbuf - diff --git a/config/fabric/modules/bar/widgets/volume.py b/config/fabric/modules/bar/widgets/volume.py index 8bf5d568..76d9dd57 100644 --- a/config/fabric/modules/bar/widgets/volume.py +++ b/config/fabric/modules/bar/widgets/volume.py @@ -2,64 +2,82 @@ from fabric.audio.service import Audio from fabric.utils import exec_shell_command, invoke_repeater from fabric.widgets.box import Box from fabric.widgets.button import Button +from icon import MaterialIcon +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) - audio_service = Audio() - - self.audio_service = audio_service - self.volume_icon = Button( - label="󰖁", - style="margin-left:6px; font-size:18px;", - # name="symbol", - ) - - # Connect mouse button events to handle clicks - self.volume_icon.connect("button-press-event", self.on_button_press) + 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) + invoke_repeater(1000, self.update_volume_status, initial_call=True) - self.children = (self.volume_icon,) + self.children = (self.volume_icon_button,) + + def create_volume_icon(self): + """Create a button with a Material icon for volume.""" + icon = MaterialIcon(VOLUME_ICONS["muted"], size="16px") + button = Button(child=icon) + button.icon_widget = icon + return button def on_button_press(self, widget, event): if event.button == 1: - self.on_clicked() + self.toggle_mute() elif event.button == 3: - self.on_right_clicked() - elif event.button == 2: - self.on_middle_clicked() + self.open_audio_settings() - def on_right_clicked(self): - exec_shell_command("pavucontrol") - - def on_clicked(self): + 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 on_middle_clicked(self): - # exec_shell_command("pavucontrol") + def open_audio_settings(self): + exec_shell_command("pavucontrol") def update_volume_status(self, *args): current_stream = self.audio_service.speaker if current_stream: - volume_level = current_stream.volume - is_muted = current_stream.muted - - if is_muted: - self.volume_icon.set_label("󰖁") - else: - if volume_level == 0: - self.volume_icon.set_label("󰝟") - elif volume_level <= 33: - self.volume_icon.set_label("󰕿") - elif volume_level <= 66: - self.volume_icon.set_label("󰖀") - else: - self.volume_icon.set_label("󰕾") + self.update_icon(current_stream) return True + + def update_icon(self, stream): + """Update the icon based on the current stream status.""" + 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): + """Determine the appropriate icon based on the 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"] + diff --git a/config/fabric/modules/bar/widgets/wifi.py b/config/fabric/modules/bar/widgets/wifi.py deleted file mode 100644 index 5b782f6b..00000000 --- a/config/fabric/modules/bar/widgets/wifi.py +++ /dev/null @@ -1,42 +0,0 @@ -import socket - -import psutil -from fabric.utils import invoke_repeater -from fabric.widgets.box import Box -from fabric.widgets.label import Label - - -class Wifi(Box): - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.wifi_icon = Label( - label="󰤭", - name="symbol", - # style="font-size: 16px; margin-right: 6px;", - ) - - invoke_repeater(1000, self.update_wifi_status, initial_call=True) - - self.children = (self.wifi_icon,) - - def update_wifi_status(self): - wifi_on = self.check_wifi_status() - - if wifi_on: - self.wifi_icon.set_label("󰤥") - else: - self.wifi_icon.set_label("󰤭") - - return True - - def check_wifi_status(self): - for interface, addrs in psutil.net_if_addrs().items(): - if "wlan" in interface or "wifi" in interface: - if ( - interface in psutil.net_if_stats() - and psutil.net_if_stats()[interface].isup - ): - for addr in addrs: - if addr.family in (socket.AF_INET, socket.AF_INET6): - return True - return False diff --git a/config/fabric/modules/bar/widgets/workspace.py b/config/fabric/modules/bar/widgets/workspace.py new file mode 100644 index 00000000..f13372d6 --- /dev/null +++ b/config/fabric/modules/bar/widgets/workspace.py @@ -0,0 +1,19 @@ +import json + +from fabric.hyprland.service import Hyprland +from fabric.widgets.label import Label + +connection = Hyprland() + +workspaceData = connection.send_command("j/activeworkspace").reply +activeWorkspace = json.loads(workspaceData.decode("utf-8"))["name"] +workspace = Label(label=f"Workspace {activeWorkspace}") + + +def on_workspace(obj, signal): + global activeWorkspace + activeWorkspace = json.loads(signal.data[0]) + workspace.set_label(f"Workspace {activeWorkspace}") + + +connection.connect("event::workspace", on_workspace)