materialicon
This commit is contained in:
1 parent
cf3801814c
commit
9bc0630a1a
29 files changed
+324
-536
No files matched your search
@@ -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)
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
@@ -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
|
||||
)
|
||||
@@ -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 *
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -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}%")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
Reference in new issue
Block a user