import os import subprocess from fabric.hyprland.widgets import HyprlandActiveWindow as ActiveWindow from fabric.utils import FormattedString from fabric.widgets.box import Box from fabric.widgets.button import Button from fabric.widgets.centerbox import CenterBox from fabric.widgets.label import Label from modules.about import About from utils.roam import modus_service from widgets.dropdown import ModusDropdown, dropdown_divider from widgets.mousecapture import DropDownMouseCapture class AppName: def __init__(self, path="/usr/share/applications"): self.files = os.listdir(path) self.path = path def get_app_name(self, wmclass, format_=False): desktop_file = "" for f in self.files: if f.startswith(wmclass + ".desktop"): desktop_file = f desktop_app_name = wmclass if desktop_file == "": return wmclass with open(os.path.join(self.path, desktop_file), "r") as f: lines = f.readlines() for line in lines: if line.startswith("Name="): desktop_app_name = line.split("=")[1].strip() break return desktop_app_name def get_app_exec(self, wmclass, format_=False): desktop_file = "" for f in self.files: if f.startswith(wmclass + ".desktop"): desktop_file = f desktop_app_name = wmclass if desktop_file == "": return wmclass with open(os.path.join(self.path, desktop_file), "r") as f: lines = f.readlines() for line in lines: if line.startswith("Exec="): desktop_app_name = line.split("=")[1].strip() break return desktop_app_name def get_desktop_file(self, wmclass): desktop_file = "" for f in self.files: if f.startswith(wmclass + ".desktop"): desktop_file = f return desktop_file def format_app_name(self, title, wmclass, update=False): # Handle case when both title and wmclass are empty (no active window) if not title and not wmclass: name = "Hyprland" else: name = wmclass if name == "": name = title # Try to get the proper app name from desktop file only if wmclass is not empty if wmclass: name = self.get_app_name(wmclass=wmclass) # Smart title formatting (capitalize first letter) name = str(name).title() if "." in name: name = name.split(".")[-1] if update: modus_service.current_active_app_name = name return name app_name_class = AppName() def format_window(title, wmclass): name = app_name_class.format_app_name(title, wmclass, True) return name def dropdown_option( label: str = "", keybind: str = "", on_click='echo "ModusPanelDropdown Action"', on_clicked=None, ): def on_click_subthread(button): # Execute the action first if on_clicked: on_clicked(button) else: subprocess.Popen( f"nohup {on_click} &", shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, ) # Hide dropdown by finding the current visible dropdown and calling its hide method from widgets.dropdown import dropdowns for dropdown in dropdowns: if dropdown.is_visible() and hasattr(dropdown, "hide_via_mousecapture"): dropdown.hide_via_mousecapture() break return Button( child=CenterBox( start_children=[ Label(label=label, h_align="start", name="dropdown-option-label"), ], end_children=[ Label(label=keybind, h_align="end", name="dropdown-option-keybind") ], orientation="horizontal", h_align="fill", h_expand=True, v_expand=True, ), name="dropdown-option", h_align="fill", on_clicked=on_click_subthread, h_expand=True, v_expand=True, ) class SystemDropdown(ModusDropdown): def __init__(self, parent, **kwargs): super().__init__( dropdown_id="os-menu", parent=parent, dropdown_children=[ dropdown_option( "About this PC", on_clicked=lambda _: About().toggle(_) ), dropdown_divider("---------------------"), dropdown_option( "System Settings...", # TODO: Open Modus own setting # on_click="xdg-open settings", ), dropdown_divider("---------------------"), dropdown_option("Force Quit", "", "hyprctl kill"), dropdown_divider("---------------------"), dropdown_option("Sleep", "", "systemctl suspend"), dropdown_option("Restart...", "", "systemctl reboot"), dropdown_option("Shut Down...", "", "shutdown now"), dropdown_divider("---------------------"), dropdown_option("Lock Screen", "󰘳 L", "hyprlock"), ], **kwargs, ) class MenuBarDropdowns: def __init__(self, parent): self.parent = parent # System dropdown self.system_dropdown = SystemDropdown(parent=parent) self.menu_button_dropdown = DropDownMouseCapture( layer="bottom", child_window=self.system_dropdown ) self.menu_button = Button( label="Modus", name="menu-button", style_classes="button", on_clicked=lambda _: self.menu_button_dropdown.toggle_mousecapture(), ) self.menu_button_dropdown.child_window.set_pointing_to(self.menu_button) # Global menu dropdowns self.global_title_menu_about = dropdown_option( f"About {modus_service.current_active_app_name}" ) self.global_menu_title = DropDownMouseCapture( layer="bottom", child_window=ModusDropdown( dropdown_id="global-menu-title", parent=parent, dropdown_children=[self.global_title_menu_about], ), ) self.global_menu_file = None self.global_menu_edit = None self.global_menu_view = DropDownMouseCapture( layer="bottom", child_window=ModusDropdown( dropdown_id="global-menu-view", parent=parent, dropdown_children=[ dropdown_option( "Enter Full Screen", on_click="hyprctl dispatch fullscreen", ), ], ), ) self.global_menu_go = None self.global_menu_window = DropDownMouseCapture( layer="bottom", child_window=ModusDropdown( dropdown_id="global-menu-window", parent=parent, dropdown_children=[ dropdown_option( "Zoom In", "󰍉 +", on_click="hyprctl -q keyword cursor:zoom_factor $(hyprctl getoption cursor:zoom_factor -j | jq '.float * 1.1')", ), dropdown_option( "Zoom Out", "󰍉 -", on_click="hyprctl -q keyword cursor:zoom_factor $(hyprctl getoption cursor:zoom_factor -j | jq '(.float * 0.9) | if . < 1 then 1 else . end')", ), dropdown_divider("---------------------"), dropdown_option( "Move Window to Left", on_click="hyprctl dispatch movewindow l", ), dropdown_option( "Move Window to Right", on_click="hyprctl dispatch movewindow r", ), dropdown_option( "Cycle Through Windows", on_click="hyprctl dispatch cyclenext", ), dropdown_divider("---------------------"), dropdown_option( "Float", on_click="hyprctl dispatch togglefloating" ), dropdown_option("Quit", on_click="hyprctl dispatch killactive"), dropdown_option("Pseudo", on_click="hyprctl dispatch pseudo"), dropdown_option( "Toggle Split", on_click="hyprctl dispatch togglesplit" ), dropdown_option("Center", on_click="hyprctl dispatch centerwindow"), dropdown_option("Group", on_click="hyprctl dispatch togglegroup"), dropdown_option( "Pin", on_clicked=lambda _: subprocess.run( "bash ~/.config/scripts/winpin.sh", shell=True ), ), ], ), ) self.global_menu_help = DropDownMouseCapture( layer="bottom", child_window=ModusDropdown( dropdown_id="global-menu-help", parent=parent, dropdown_children=[ dropdown_option( "Modus", on_click="xdg-open https://github.com/S4NKALP/Modus/issues", ), dropdown_divider("---------------------"), dropdown_option( "Hyprland Wiki", on_click="xdg-open https://wiki.hyprland.org/" ), ], ), ) # Create menu buttons modus_service.connect( "current-active-app-name-changed", lambda _, value: self.global_title_menu_about.set_property( "label", f"About {value}" ), ) self.global_menu_button_title = Button( child=ActiveWindow( formatter=FormattedString( "{ format_window(win_title, win_class) if win_title or win_class else 'Hyprland' }", format_window=format_window, ) ), name="global-title-button", style_classes="button", on_clicked=lambda _: self.global_menu_title.toggle_mousecapture(), ) self.global_menu_title.child_window.set_pointing_to( self.global_menu_button_title ) self.global_menu_button_file = Button( label="File", name="global-menu-button-file", style_classes="button" ) self.global_menu_button_edit = Button( label="Edit", name="global-menu-button-edit", style_classes="button" ) self.global_menu_button_view = Button( label="View", name="global-menu-button-view", style_classes="button", on_clicked=lambda _: self.global_menu_view.toggle_mousecapture(), ) self.global_menu_view.child_window.set_pointing_to(self.global_menu_button_view) self.global_menu_button_go = Button( label="Go", name="global-menu-button-go", style_classes="button" ) self.global_menu_button_window = Button( label="Window", name="global-menu-button-window", style_classes="button", on_clicked=lambda _: self.global_menu_window.toggle_mousecapture(), ) self.global_menu_window.child_window.set_pointing_to( self.global_menu_button_window ) self.global_menu_button_help = Button( label="Help", name="global-menu-button-help", style_classes="button", on_clicked=lambda _: self.global_menu_help.toggle_mousecapture(), ) self.global_menu_help.child_window.set_pointing_to(self.global_menu_button_help) modus_service.connect("current-dropdown-changed", self.changed_dropdown) modus_service.connect("dropdowns-hide-changed", self.hide_dropdowns) def hide_dropdowns(self, *_): self.menu_button.remove_style_class("active") self.global_menu_button_edit.remove_style_class("active") self.global_menu_button_file.remove_style_class("active") self.global_menu_button_go.remove_style_class("active") self.global_menu_button_help.remove_style_class("active") self.global_menu_button_title.remove_style_class("active") self.global_menu_button_view.remove_style_class("active") self.global_menu_button_window.remove_style_class("active") def changed_dropdown(self, _, dropdown_id): self.hide_dropdowns(_, True) match dropdown_id: case "os-menu": self.menu_button.add_style_class("active") case "global-menu-edit": self.global_menu_button_edit.add_style_class("active") case "global-menu-file": self.global_menu_button_file.add_style_class("active") case "global-menu-go": self.global_menu_button_go.add_style_class("active") case "global-menu-help": self.global_menu_button_help.add_style_class("active") case "global-menu-title": self.global_menu_button_title.add_style_class("active") case "global-menu-view": self.global_menu_button_view.add_style_class("active") case "global-menu-window": self.global_menu_button_window.add_style_class("active") case _: pass class MenuBar(Box): """Main MenuBar widget that contains all menu buttons""" def __init__(self, parent_window=None, **kwargs): # Extract parent_window from kwargs if not provided as parameter if parent_window is None: parent_window = kwargs.pop("parent_window", None) super().__init__(name="menubar", orientation="horizontal", spacing=0, **kwargs) # Create the dropdown system self.dropdown_system = MenuBarDropdowns(parent=parent_window) # Create combined workspace and app label self.workspace_label = Label( label=f"{modus_service.current_workspace} -", name="workspace-name", style_classes="workspace-label", ) # Connect to workspace changes modus_service.connect( "current-workspace-changed", lambda _, value: self.workspace_label.set_label(f"{value} -"), ) # Add all the menu buttons to the menubar self.children = [ self.workspace_label, self.dropdown_system.global_menu_button_title, self.dropdown_system.global_menu_button_file, self.dropdown_system.global_menu_button_edit, self.dropdown_system.global_menu_button_view, self.dropdown_system.global_menu_button_go, self.dropdown_system.global_menu_button_window, self.dropdown_system.global_menu_button_help, ] def show_system_dropdown(self, imac_button): self.dropdown_system.menu_button_dropdown.child_window.set_pointing_to( imac_button ) mouse_capture = self.dropdown_system.menu_button_dropdown if mouse_capture.is_visible(): mouse_capture.set_child_window_visible(False) else: mouse_capture.set_child_window_visible(True)