Files
Modus/bar.py
T
2025-06-07 15:11:26 +05:45

565 lines
18 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import json
import os
from fabric.hyprland.service import HyprlandEvent
from fabric.hyprland.widgets import (
Language,
WorkspaceButton,
Workspaces,
get_hyprland_connection,
)
from fabric.utils.helpers import exec_shell_command_async
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.label import Label
from fabric.widgets.revealer import Revealer
from gi.repository import Gdk, Gtk
import config.data as data
import modules.icons as icons
from modules.controls import ControlSmall
from modules.dock import Dock
from modules.metrics import Battery, MetricsSmall, NetworkApplet
from modules.systemtray import SystemTray
from modules.weather import Weather
from widgets.wayland import WaylandWindow as Window
CHINESE_NUMERALS = ["一", "二", "三", "四", "五", "六", "七", "八", "九", "〇"]
# Tooltips
tooltip_apps = f"""<b><u>Launcher</u></b>
<b>• Apps:</b> Type to search.
<b>• Calculator [Prefix "="]:</b> Solve a math expression.
e.g. "=2+2"
<b>• Converter [Prefix ";"]:</b> Convert between units.
e.g. ";100 USD to EUR", ";10 km to miles"
<b>• Special Commands [Prefix ":"]:</b>
:update - Open {data.APP_NAME_CAP}'s updater.
:d - Open Dashboard.
:w - Open Wallpapers."""
tooltip_power = """<b>Power Menu</b>"""
tooltip_tools = """<b>Toolbox</b>"""
tooltip_overview = """<b>Overview</b>"""
class Bar(Window):
def __init__(self, **kwargs):
super().__init__(
name="bar",
layer="top",
exclusivity="auto",
visible=True,
all_visible=True,
)
self.anchor_var = ""
self.margin_var = ""
match data.BAR_POSITION:
case "Top":
self.anchor_var = "left top right"
case "Bottom":
self.anchor_var = "left bottom right"
case "Left":
self.anchor_var = "left" if data.CENTERED_BAR else "left top bottom"
case "Right":
self.anchor_var = "right" if data.CENTERED_BAR else "top right bottom"
case _:
self.anchor_var = "left top right"
if data.VERTICAL:
match data.BAR_THEME:
case "Edge":
self.margin_var = "-8px -8px -8px -8px"
case _:
self.margin_var = "-4px -8px -4px -4px"
else:
match data.BAR_THEME:
case "Edge":
self.margin_var = "-8px -8px -8px -8px"
case _:
if data.BAR_POSITION == "Bottom":
self.margin_var = "-8px -4px -4px -4px"
else:
self.margin_var = "-4px -4px -8px -4px"
self.set_anchor(self.anchor_var)
self.set_margin(self.margin_var)
self.notch = kwargs.get("notch", None)
self.component_visibility = data.BAR_COMPONENTS_VISIBILITY
self.dock_instance = None
self.integrated_dock_widget = None
self.workspaces = Workspaces(
name="workspaces",
invert_scroll=True,
empty_scroll=True,
v_align="fill",
orientation="h" if not data.VERTICAL else "v",
spacing=8,
buttons=[
WorkspaceButton(
h_expand=False,
v_expand=False,
h_align="center",
v_align="center",
id=i,
label=None,
style_classes=["vertical"] if data.VERTICAL else None,
)
for i in range(1, 11)
],
)
self.workspaces_num = Workspaces(
name="workspaces-num",
invert_scroll=True,
empty_scroll=True,
v_align="fill",
orientation="h" if not data.VERTICAL else "v",
spacing=0 if not data.BAR_WORKSPACE_USE_CHINESE_NUMERALS else 4,
buttons=[
WorkspaceButton(
h_expand=False,
v_expand=False,
h_align="center",
v_align="center",
id=i,
label=CHINESE_NUMERALS[i - 1]
if data.BAR_WORKSPACE_USE_CHINESE_NUMERALS
and 1 <= i <= len(CHINESE_NUMERALS)
else str(i),
)
for i in range(1, 11)
],
)
self.ws_container = Box(
name="workspaces-container",
children=self.workspaces
if not data.BAR_WORKSPACE_SHOW_NUMBER
else self.workspaces_num,
)
self.button_tools = Button(
name="button-bar",
tooltip_markup=tooltip_tools,
on_clicked=lambda *_: self.tools_menu(),
child=Label(name="button-bar-label", markup=icons.toolbox),
)
self.connection = get_hyprland_connection()
self.button_tools.connect("enter_notify_event", self.on_button_enter)
self.button_tools.connect("leave_notify_event", self.on_button_leave)
self.systray = SystemTray()
self.weather = Weather()
self.network = NetworkApplet()
self.lang_label = Label(name="lang-label")
self.language = Button(
name="language", h_align="center", v_align="center", child=self.lang_label
)
self.on_language_switch()
self.connection.connect("event::activelayout", self.on_language_switch)
self.date_time = DateTime(
name="date-time",
formatters=["%H:%M"] if not data.VERTICAL else ["%H\n%M"],
h_align="center" if not data.VERTICAL else "fill",
v_align="center",
h_expand=True,
v_expand=True,
)
self.button_apps = Button(
name="button-bar",
tooltip_markup=tooltip_apps,
on_clicked=lambda *_: self.search_apps(),
child=Label(name="button-bar-label", markup=icons.apps),
)
self.button_apps.connect("enter_notify_event", self.on_button_enter)
self.button_apps.connect("leave_notify_event", self.on_button_leave)
self.button_power = Button(
name="button-bar",
tooltip_markup=tooltip_power,
on_clicked=lambda *_: self.power_menu(),
child=Label(name="button-bar-label", markup=icons.shutdown),
)
self.button_power.connect("enter_notify_event", self.on_button_enter)
self.button_power.connect("leave_notify_event", self.on_button_leave)
self.button_overview = Button(
name="button-bar",
tooltip_markup=tooltip_overview,
on_clicked=lambda *_: self.overview(),
child=Label(name="button-bar-label", markup=icons.windows),
)
self.button_overview.connect("enter_notify_event", self.on_button_enter)
self.button_overview.connect("leave_notify_event", self.on_button_leave)
self.control = ControlSmall()
self.metrics = MetricsSmall()
self.battery = Battery()
self.apply_component_props()
self.rev_right = [
self.metrics,
self.control,
]
self.revealer_right = Revealer(
name="bar-revealer",
transition_type="slide-left",
child_revealed=True,
child=Box(
name="bar-revealer-box",
orientation="h",
spacing=4,
children=self.rev_right if not data.VERTICAL else None,
),
)
self.boxed_revealer_right = Box(
name="boxed-revealer",
children=[
self.revealer_right,
],
)
self.rev_left = [
self.weather,
self.network,
]
self.revealer_left = Revealer(
name="bar-revealer",
transition_type="slide-right",
child_revealed=True,
child=Box(
name="bar-revealer-box",
orientation="h",
spacing=4,
children=self.rev_left if not data.VERTICAL else None,
),
)
self.boxed_revealer_left = Box(
name="boxed-revealer",
children=[
self.revealer_left,
],
)
self.h_start_children = [
self.button_apps,
self.ws_container,
self.button_overview,
self.boxed_revealer_left,
]
self.h_end_children = [
self.boxed_revealer_right,
self.battery,
self.systray,
self.button_tools,
self.language,
self.date_time,
self.button_power,
]
self.v_start_children = [
self.button_apps,
self.systray,
self.control,
self.network,
self.button_tools,
]
self.v_center_children = [
self.button_overview,
self.ws_container,
self.weather,
]
self.v_end_children = [
self.battery,
self.metrics,
self.language,
self.date_time,
self.button_power,
]
self.v_all_children = []
self.v_all_children.extend(self.v_start_children)
self.v_all_children.extend(self.v_center_children)
self.v_all_children.extend(self.v_end_children)
if (
data.DOCK_ENABLED
and data.BAR_POSITION == "Bottom"
or data.PANEL_THEME == "Panel"
and data.BAR_POSITION in ["Top", "Bottom"]
):
if not data.VERTICAL:
self.dock_instance = Dock(integrated_mode=True)
self.integrated_dock_widget = self.dock_instance.wrapper
is_centered_bar = data.VERTICAL and getattr(data, "CENTERED_BAR", False)
bar_center_actual_children = None
if self.integrated_dock_widget is not None:
bar_center_actual_children = self.integrated_dock_widget
elif data.VERTICAL:
bar_center_actual_children = Box(
orientation=Gtk.Orientation.VERTICAL,
spacing=4,
children=self.v_all_children
if is_centered_bar
else self.v_center_children,
)
self.bar_inner = CenterBox(
name="bar-inner",
orientation=Gtk.Orientation.HORIZONTAL
if not data.VERTICAL
else Gtk.Orientation.VERTICAL,
h_align="fill",
v_align="fill",
start_children=None
if is_centered_bar
else Box(
name="start-container",
spacing=4,
orientation=Gtk.Orientation.HORIZONTAL
if not data.VERTICAL
else Gtk.Orientation.VERTICAL,
children=self.h_start_children
if not data.VERTICAL
else self.v_start_children,
),
center_children=bar_center_actual_children,
end_children=None
if is_centered_bar
else Box(
name="end-container",
spacing=4,
orientation=Gtk.Orientation.HORIZONTAL
if not data.VERTICAL
else Gtk.Orientation.VERTICAL,
children=self.h_end_children
if not data.VERTICAL
else self.v_end_children,
),
)
self.children = self.bar_inner
self.hidden = False
self.themed_children = [
self.button_apps,
self.button_overview,
self.button_power,
self.button_tools,
self.language,
self.date_time,
self.ws_container,
self.weather,
self.network,
self.battery,
self.metrics,
self.systray,
self.control,
]
if self.integrated_dock_widget:
self.themed_children.append(self.integrated_dock_widget)
current_theme = data.BAR_THEME
theme_classes = ["pills", "dense", "edge", "edgecenter"]
for tc in theme_classes:
self.bar_inner.remove_style_class(tc)
self.style = None
match current_theme:
case "Pills":
self.style = "pills"
case "Dense":
self.style = "dense"
case "Edge":
if data.VERTICAL and data.CENTERED_BAR:
self.style = "edgecenter"
else:
self.style = "edge"
case _:
self.style = "pills"
self.bar_inner.add_style_class(self.style)
if self.integrated_dock_widget and hasattr(
self.integrated_dock_widget, "add_style_class"
):
for theme_class_to_remove in ["pills", "dense", "edge"]:
style_context = self.integrated_dock_widget.get_style_context()
if style_context.has_class(theme_class_to_remove):
self.integrated_dock_widget.remove_style_class(
theme_class_to_remove
)
self.integrated_dock_widget.add_style_class(self.style)
if data.BAR_THEME == "Dense" or data.BAR_THEME == "Edge":
for child in self.themed_children:
if hasattr(child, "add_style_class"):
child.add_style_class("invert")
match data.BAR_POSITION:
case "Top":
self.bar_inner.add_style_class("top")
case "Bottom":
self.bar_inner.add_style_class("bottom")
case "Left":
self.bar_inner.add_style_class("left")
case "Right":
self.bar_inner.add_style_class("right")
case _:
self.bar_inner.add_style_class("top")
if data.VERTICAL:
self.bar_inner.add_style_class("vertical")
self.systray._update_visibility()
self.chinese_numbers()
def apply_component_props(self):
components = {
"button_apps": self.button_apps,
"systray": self.systray,
"control": self.control,
"network": self.network,
"button_tools": self.button_tools,
"button_overview": self.button_overview,
"ws_container": self.ws_container,
"weather": self.weather,
"battery": self.battery,
"metrics": self.metrics,
"language": self.language,
"date_time": self.date_time,
"button_power": self.button_power,
}
for component_name, widget in components.items():
if component_name in self.component_visibility:
widget.set_visible(self.component_visibility[component_name])
def toggle_component_visibility(self, component_name):
components = {
"button_apps": self.button_apps,
"systray": self.systray,
"control": self.control,
"network": self.network,
"button_tools": self.button_tools,
"button_overview": self.button_overview,
"ws_container": self.ws_container,
"weather": self.weather,
"battery": self.battery,
"metrics": self.metrics,
"language": self.language,
"date_time": self.date_time,
"button_power": self.button_power,
}
if component_name in components and component_name in self.component_visibility:
self.component_visibility[component_name] = not self.component_visibility[
component_name
]
components[component_name].set_visible(
self.component_visibility[component_name]
)
config_file = os.path.expanduser(
f"~/.config/{data.APP_NAME}/config/config.json"
)
if os.path.exists(config_file):
try:
with open(config_file, "r") as f:
config = json.load(f)
config[f"bar_{component_name}_visible"] = self.component_visibility[
component_name
]
with open(config_file, "w") as f:
json.dump(config, f, indent=4)
except Exception as e:
print(f"Error updating config file: {e}")
return self.component_visibility[component_name]
return None
def on_button_enter(self, widget, event):
window = widget.get_window()
if window:
window.set_cursor(Gdk.Cursor.new_from_name(widget.get_display(), "hand2"))
def on_button_leave(self, widget, event):
window = widget.get_window()
if window:
window.set_cursor(None)
def on_button_clicked(self, *args):
exec_shell_command_async("notify-send 'Botón presionado' '¡Funciona!'")
def search_apps(self):
if self.notch:
self.notch.open_notch("launcher")
def overview(self):
if self.notch:
self.notch.open_notch("overview")
def power_menu(self):
if self.notch:
self.notch.open_notch("power")
def tools_menu(self):
if self.notch:
self.notch.open_notch("tools")
def on_language_switch(self, _=None, event: HyprlandEvent = None):
lang_data = (
event.data[1]
if event and event.data and len(event.data) > 1
else Language().get_label()
)
self.language.set_tooltip_text(lang_data)
if not data.VERTICAL:
self.lang_label.set_label(lang_data[:3].upper())
else:
self.lang_label.add_style_class("icon")
self.lang_label.set_markup(icons.keyboard)
def toggle_hidden(self):
self.hidden = not self.hidden
if self.hidden:
self.bar_inner.add_style_class("hidden")
else:
self.bar_inner.remove_style_class("hidden")
def chinese_numbers(self):
if data.BAR_WORKSPACE_USE_CHINESE_NUMERALS:
self.workspaces_num.add_style_class("chinese")
else:
self.workspaces_num.remove_style_class("chinese")