✨ feat: implement core functionality and UI for Modus shell

This commit is contained in:
sankalp committed 2025-07-30 09:28:32 +05:45
1 parent 5789013e2e
commit acec6bdda7
129 files changed
+33424 -226

No files matched your search

+295
View File
@@ -0,0 +1,295 @@
from fabric.bluetooth import BluetoothClient
from fabric.widgets.box import Box
from fabric.widgets.button import Button
from fabric.widgets.label import Label
from fabric.utils import get_relative_path
from fabric.widgets.svg import Svg
import utils.icons as icons
from services.network import NetworkClient
from gi.repository import GLib
from services.battery import Battery as BatteryService
class WifiIndicator(Button):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.network = NetworkClient()
self.wifi_icon = Svg(
name="indicators-icon",
size=24,
svg_file=get_relative_path("../../../config/assets/icons/wifi.svg"),
)
self.add(self.wifi_icon)
self.network.connect("ready", self.on_network_ready)
if self.network.is_ready:
self.setup_signals()
def on_network_ready(self, *args):
self.setup_signals()
def setup_signals(self):
self.network.connect("changed", self.on_network_changed)
self.network.connect("wifi-device-added", self.on_wifi_device_added)
self.network.connect("wifi-device-removed", self.on_wifi_device_removed)
self.network.connect("ethernet-device-added", self.on_ethernet_device_added)
self.network.connect("ethernet-device-removed", self.on_ethernet_device_removed)
if self.network.wifi_device:
self.network.wifi_device.connect("changed", self.on_wifi_changed)
self.network.wifi_device.connect("ap-added", self.on_wifi_changed)
self.network.wifi_device.connect("ap-removed", self.on_wifi_changed)
if self.network.ethernet_device:
self.network.ethernet_device.connect("changed", self.on_ethernet_changed)
self.update_state()
def update_state(self):
if (
self.network.ethernet_device
and self.network.ethernet_device.state == "activated"
):
self.wifi_icon.set_from_file(get_relative_path("../../../config/assets/icons/wifi.svg"))
tooltip = f"Connected to Ethernet ({self.network.ethernet_device.speed})"
self.set_tooltip_text(tooltip)
return
if not self.network.wifi_device:
self.wifi_icon.set_from_file(get_relative_path("../../../config/assets/icons/wifi-off.svg"))
tooltip = "No WiFi device found"
elif not self.network.wifi_device.wireless_enabled:
self.wifi_icon.set_from_file(get_relative_path("../../../config/assets/icons/wifi-off.svg"))
tooltip = "WiFi disabled"
else:
active_ap = self.network.wifi_device.active_access_point
if active_ap:
strength = active_ap.strength
# Use wifi.svg for all connected states since we don't have signal strength icons
self.wifi_icon.set_from_file(get_relative_path("../../../config/assets/icons/wifi.svg"))
tooltip = f"Connected to {active_ap.ssid} ({strength}%)"
else:
self.wifi_icon.set_from_file(get_relative_path("../../../config/assets/icons/wifi-off.svg"))
tooltip = "Not connected"
self.set_tooltip_text(tooltip)
def on_wifi_device_added(self, *args):
self.network.wifi_device.connect("changed", self.on_wifi_changed)
self.network.wifi_device.connect("ap-added", self.on_wifi_changed)
self.network.wifi_device.connect("ap-removed", self.on_wifi_changed)
self.update_state()
def on_wifi_device_removed(self, *args):
self.update_state()
def on_network_changed(self, *args):
self.update_state()
def on_wifi_changed(self, *args):
self.update_state()
def on_ethernet_device_added(self, *args):
if self.network.ethernet_device:
self.network.ethernet_device.connect("changed", self.on_ethernet_changed)
self.update_state()
def on_ethernet_device_removed(self, *args):
self.update_state()
def on_ethernet_changed(self, *args):
self.update_state()
class BluetoothIndicator(Button):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.bluetooth = BluetoothClient()
# Use SVG icons instead of font icons
self.bt_icon = Svg(
name="bt-icon",
size=20,
svg_file=get_relative_path("../../../config/assets/icons/bluetooth.svg"),
)
self.add(self.bt_icon)
self.bluetooth.connect("changed", self.on_bluetooth_changed)
self.bluetooth.connect("device-added", self.on_device_added)
self.bluetooth.connect("device-removed", self.on_device_removed)
self.update_state()
def update_state(self):
if not self.bluetooth.enabled:
self.bt_icon.set_from_file(get_relative_path("../../../config/assets/icons/bluetooth-off.svg"))
tooltip = "Bluetooth disabled"
else:
connected_devices = self.bluetooth.connected_devices
if connected_devices:
self.bt_icon.set_from_file(get_relative_path("../../../config/assets/icons/bluetooth.svg"))
if len(connected_devices) == 1:
device = connected_devices[0]
tooltip = f"Connected to {device.alias}"
if device.battery_percentage > 0:
tooltip += f" ({device.battery_percentage:.0f}%)"
else:
tooltip = f"Connected to {len(connected_devices)} devices"
else:
self.bt_icon.set_from_file(get_relative_path("../../../config/assets/icons/bluetooth.svg"))
tooltip = "No devices connected"
self.set_tooltip_text(tooltip)
def on_bluetooth_changed(self, *args):
self.update_state()
def on_device_added(self, _, address):
self.update_state()
def on_device_removed(self, _, address):
self.update_state()
class Battery(Box):
def __init__(self, **kwargs):
super().__init__(
orientation="v", visible=True, **kwargs
)
self._battery = BatteryService()
self._battery.changed.connect(self.update_battery)
self.icon = Svg(
name="indicators-icon",
size=24,
svg_file=get_relative_path("../../../config/assets/icons/battery/battery-100.svg"),
)
self.battery_button = Button(
child=self.icon,
)
self.battery_box = Box(
name="battery-box",
orientation="h",
children=[self.battery_button],
)
self.battery_button.set_has_tooltip(True)
self.battery_box.set_has_tooltip(True)
self.battery_button.connect("query-tooltip", self.on_query_tooltip)
self.battery_box.connect("query-tooltip", self.on_query_tooltip)
self.add(self.battery_box)
GLib.idle_add(self.update_battery)
def on_query_tooltip(self, *_):
tooltip = _[-1]
tooltip.set_markup(self.get_tooltip_text())
return True
def get_tooltip_text(self):
if not self._battery.is_present:
return "No battery detected"
percentage = self._battery.percentage
state = self._battery.state
capacity = int(float(self._battery.capacity.rstrip("%")))
tooltip_points = []
if state == "FULLY_CHARGED":
status = f"{icons.battery_full} Fully Charged"
elif state == "CHARGING":
status = f"{icons.bat_charging} Charging"
elif percentage <= 15 and state == "DISCHARGING":
status = f"{icons.bat_alert} Low Battery"
elif state == "DISCHARGING":
status = f"{icons.battery_0} Discharging"
else:
status = "Battery"
tooltip_points.append(f"• Status: {status}")
tooltip_points.append(f"• Level: {int(percentage)}%")
tooltip_points.append(f"• Battery Health: {capacity}%")
power_profile = self._battery.power_profile
if power_profile:
if power_profile == "power-saver":
profile_icon = icons.power_saving
elif power_profile == "performance":
profile_icon = icons.power_performance
else:
profile_icon = icons.power_balanced
tooltip_points.append(f"• Profile: {profile_icon} {power_profile}")
temp = self._battery.temperature
if temp != "N/A":
tooltip_points.append(f"• Temperature: {temp}")
if state == "CHARGING":
time_to_full = self._battery.time_to_full
if time_to_full:
tooltip_points.append(f"• Time until full: {time_to_full}")
elif state == "DISCHARGING":
time_to_empty = self._battery.time_to_empty
if time_to_empty:
tooltip_points.append(f"• Time remaining: {time_to_empty}")
return "\n".join(tooltip_points)
def update_battery(self, *_):
if not self._battery.is_present:
self.set_visible(False)
return True
percentage = self._battery.percentage
state = self._battery.state
charging = state in ["CHARGING", "FULLY_CHARGED"]
if percentage <= 15 and not charging:
self.icon.add_style_class("alert")
else:
self.icon.remove_style_class("alert")
if state == "FULLY_CHARGED" or (percentage >= 100 and state == "CHARGING"):
self.icon.set_from_file(get_relative_path("../../../config/assets/icons/battery/battery-100-charging.svg"))
elif state == "CHARGING":
# Calculate battery level and use corresponding charging icon
level = min(100, max(0, int(percentage // 10) * 10))
if level == 0 and percentage > 0:
level = 10
icon_name = f"battery-{level:03d}-charging.svg"
self.icon.set_from_file(get_relative_path(f"../../../config/assets/icons/battery/{icon_name}"))
else:
# Calculate battery level and use corresponding icon
level = min(100, max(0, int(percentage // 10) * 10))
if level == 0 and percentage > 0:
level = 10
icon_name = f"battery-{level:03d}.svg"
self.icon.set_from_file(get_relative_path(f"../../../config/assets/icons/battery/{icon_name}"))
self.set_visible(True)
return True
class Indicators(Box):
def __init__(self, **kwargs):
super().__init__(
name="indicators",
orientation="h",
spacing=10,
children=[
Battery(),
WifiIndicator(),
BluetoothIndicator(),
],
**kwargs,
)
self.show_all()
+95
View File
@@ -0,0 +1,95 @@
"""
Menu action handlers for the macOS-style menu bar.
This file contains all the implementations for menu item actions.
"""
from fabric.hyprland.service import Hyprland
from fabric.utils import exec_shell_command
class MenuActionHandler:
def __init__(self):
pass
def execute_action(self, action):
try:
# Window menu actions
if action == "Move Window to Left":
Hyprland.send_command("dispatch movewindow l")
elif action == "Move Window to Right":
Hyprland.send_command("dispatch movewindow r")
elif action == "Cycle Through Windows":
Hyprland.send_command("dispatch cyclenext")
elif action == "Float":
Hyprland.send_command("dispatch togglefloating")
elif action == "Pseudo":
Hyprland.send_command("dispatch pseudo")
elif action == "Center":
Hyprland.send_command("dispatch centerwindow")
elif action == "Group":
Hyprland.send_command("dispatch togglegroup")
elif action == "Pin":
Hyprland.send_command("dispatch pin")
elif action == "Quit":
Hyprland.send_command("dispatch killactive")
# Go menu actions
elif action == "Back":
Hyprland.send_command("dispatch workspace e-1")
elif action == "Forward":
Hyprland.send_command("dispatch workspace e+1")
# Edit menu actions
# View menu actions
elif action == "Full Screen":
Hyprland.send_command("dispatch fullscreen")
elif action == "Zoom In":
exec_shell_command("wtype -k ctrl+plus")
elif action == "Zoom Out":
exec_shell_command("wtype -k ctrl+minus")
elif action == "Actual Size":
exec_shell_command("wtype -k ctrl+0")
# Help menu actions
elif action == "Hyprland Help":
exec_shell_command("xdg-open https://wiki.hyprland.org/")
elif action == "Arch Wiki":
exec_shell_command("xdg-open https://wiki.archlinux.org/")
elif action == "Report a Bug...":
exec_shell_command("xdg-open https://github.com/S4NKALP/Modus/issues")
# System menu actions
elif action == "About This PC":
exec_shell_command("gnome-system-monitor")
elif action == "Force Quit":
exec_shell_command("gnome-system-monitor")
elif action == "Shutdown":
exec_shell_command("systemctl poweroff")
elif action == "Restart":
exec_shell_command("systemctl reboot")
elif action == "Sleep":
exec_shell_command("systemctl suspend")
elif action == "Lock":
exec_shell_command("hyprlock")
# App menu actions (for hiding apps)
elif action.startswith("Hide ") and not action == "Hide Others":
action.replace("Hide ", "")
Hyprland.send_command("dispatch movetoworkspacesilent special:hidden")
elif action == "Hide Others":
Hyprland.send_command("dispatch movetoworkspacesilent special:hidden,^(activewindow)")
elif action == "Show All":
Hyprland.send_command("dispatch movetoworkspace e+0,special:hidden")
elif action.startswith("Quit "):
action.replace("Quit ", "")
Hyprland.send_command("dispatch killactive")
else:
print(f"No implementation for action: {action}")
except Exception as e:
print(f"Error executing command for '{action}': {e}")
+75
View File
@@ -0,0 +1,75 @@
"""
Menu contents definitions for the macOS-style menu bar.
This file contains all the menu items and their organization.
"""
def get_default_menu_contents():
return {
"Hyprland": [
],
"File": [
],
"Edit": [
],
"View": [
"Full Screen",
"---",
"Zoom In",
"Zoom Out",
"Actual Size"
],
"Go": [
"Back",
"Forward",
],
"Window": [
"Move Window to Left",
"Move Window to Right",
"Cycle Through Windows",
"---",
"Float",
"Pseudo",
"Center",
"Group",
"Pin",
"Quit"
],
"Help": [
"---",
"Hyprland Help",
"Arch Wiki",
"Keyboard Shortcuts",
"---",
"Report a Bug..."
]
}
def get_app_menu_template():
return [
"About {app_name}",
"---",
"Hide {app_name}",
"---",
"Quit {app_name}"
]
def create_app_menu(app_name):
template = get_app_menu_template()
return [item.format(app_name=app_name) if "{app_name}" in item else item for item in template]
def get_menu_contents_for_app(app_name):
contents = get_default_menu_contents().copy()
if app_name and app_name != "Hyprland":
# Replace the Hyprland menu with app-specific menu
contents["Hyprland"] = create_app_menu(app_name)
return contents
def get_app_specific_menu_contents(app_name):
return get_menu_contents_for_app(app_name)
+362
View File
@@ -0,0 +1,362 @@
from fabric.widgets.box import Box
from fabric.widgets.button import Button
from fabric.widgets.label import Label
from fabric.hyprland.widgets import HyprlandActiveWindow
from utils.wayland import WaylandWindow
from gi.repository import Gtk, GLib
from .menu_contents import get_default_menu_contents, get_app_specific_menu_contents
from .menu_actions import MenuActionHandler
class DropdownWindow(WaylandWindow):
def __init__(self, x=0, y=0, parent_menubar=None, **kwargs):
super().__init__(
name="dropdown-window",
title="Menu Dropdown",
layer="overlay",
anchor="top left",
exclusivity="none",
margin=f"{y}px 0px 0px {x}px",
visible=False,
all_visible=True,
**kwargs
)
# Store reference to parent menubar for closing dropdown
self.parent_menubar = parent_menubar
self.content_box = Box(
name="dropdown-content",
orientation="v",
spacing=2,
)
self.add(self.content_box)
self.connect("enter-notify-event", self.on_mouse_enter)
self.connect("leave-notify-event", self.on_mouse_leave)
self.auto_close_timer = None
def on_mouse_enter(self, widget, event):
# Only handle if the event is for the main dropdown window, not child widgets
if event.detail != 2: # 2 = Gdk.NotifyType.INFERIOR (child widget)
# Cancel auto-close timer when mouse enters
if self.auto_close_timer:
GLib.source_remove(self.auto_close_timer)
self.auto_close_timer = None
return False
def on_mouse_leave(self, widget, event):
# Only handle if actually leaving the dropdown window, not entering child widgets
if event.detail != 2: # 2 = Gdk.NotifyType.INFERIOR (child widget)
# Start auto-close timer when mouse leaves
if not self.auto_close_timer:
self.auto_close_timer = GLib.timeout_add(300, self.auto_close_callback) # 300ms delay
return False
def auto_close_callback(self):
if self.parent_menubar:
self.parent_menubar.hide_dropdown()
self.auto_close_timer = None
return False
class MenuBar(Box):
def __init__(self, **kwargs):
super().__init__(
name="menubar",
orientation="h",
spacing=0,
**kwargs,
)
self.active_window = HyprlandActiveWindow(name="hyprland-window")
self.active_window.connect("window-activated", self.on_window_changed)
self.current_window_class = None
self.current_window_title = None
self.current_dropdown = None
self.dropdown_window = None
self.action_handler = MenuActionHandler()
# Menu items in macOS style order (excluding the first item which will be dynamic)
self.static_menu_items = [
"File",
"Edit",
"View",
"Go",
"Window",
"Help"
]
self.menu_contents = get_default_menu_contents()
self.app_button = Button(
name="menubar-button",
child=Label(
name="activewindow-label",
label="Hyprland"
),
)
self.app_button.connect("clicked", lambda widget: self.toggle_dropdown("Hyprland", widget))
self.add(self.app_button)
self.menu_buttons = []
for item in self.static_menu_items:
button = Button(
name="menubar-button",
child=Label(
name="menubar-label",
label=item
),
)
button.connect("clicked", lambda widget, menu_item=item: self.on_menu_button_clicked(menu_item, widget))
self.menu_buttons.append(button)
self.add(button)
self.update_menu_button_states()
self.show_all()
def get_button_for_item(self, menu_item):
if menu_item == "Hyprland":
return self.app_button
for i, item in enumerate(self.static_menu_items):
if item == menu_item:
return self.menu_buttons[i]
return None
def on_menu_button_clicked(self, menu_item, button):
# Check if we should allow this menu to be opened
if not self.should_menu_be_active(menu_item):
return
# Menu is allowed, proceed with normal toggle
self.toggle_dropdown(menu_item, button)
def should_menu_be_active(self, menu_item):
# These menus should only work when there's an active window
context_dependent_menus = ["File", "Edit", "View", "Go"]
if menu_item in context_dependent_menus:
# Only allow if there's an active window (not just showing "Hyprland")
has_active_window = (
self.current_window_title and
self.current_window_title.strip() and
self.app_button.get_child().get_label() != "Hyprland"
)
return has_active_window
# Window and Help menus are always available
return True
def update_menu_button_states(self):
context_dependent_menus = ["File", "Edit", "View", "Go"]
for i, menu_item in enumerate(self.static_menu_items):
if menu_item in context_dependent_menus:
button = self.menu_buttons[i]
is_active = self.should_menu_be_active(menu_item)
# Keep buttons sensitive for hovering, but add visual styling for inactive state
# Don't use set_sensitive(False) as it prevents hovering
if is_active:
button.get_style_context().remove_class("inactive")
else:
button.get_style_context().add_class("inactive")
def toggle_dropdown(self, menu_item, button):
# If same menu is clicked and dropdown is visible, hide it
if (self.current_dropdown == menu_item and
self.dropdown_window and
self.dropdown_window.get_visible()):
self.hide_dropdown()
return
# (show_dropdown will handle hiding the previous one)
self.show_dropdown(menu_item, button)
def show_dropdown(self, menu_item, button):
# Hide existing dropdown if any and wait for it to be completely hidden
if self.dropdown_window:
self.dropdown_window.set_visible(False)
self.dropdown_window.destroy()
self.dropdown_window = None
# Wait a short moment to ensure the previous dropdown is completely gone
GLib.timeout_add(150, self._create_and_show_dropdown, menu_item, button)
else:
# No existing dropdown, show immediately
self._create_and_show_dropdown(menu_item, button)
def _create_and_show_dropdown(self, menu_item, button):
# Calculate position first
x, y = self.calculate_dropdown_position(button)
# Create new dropdown window at the calculated position
self.dropdown_window = DropdownWindow(x=x, y=y, parent_menubar=self)
# Get menu items for this menu
menu_items = self.menu_contents.get(menu_item, [])
# Add menu items to dropdown
for item in menu_items:
if item == "---":
# Add separator
separator = Box(
name="menu-separator",
size=(150, 1),
style="background-color: #444; margin: 2px 0;"
)
self.dropdown_window.content_box.add(separator)
else:
# Add menu item button
menu_button = Button(
name="dropdown-item",
child=Label(
name="dropdown-label",
label=item,
h_align="start" # Align text to the left
),
on_clicked=lambda *_, action=item: self.on_menu_action(action)
)
self.dropdown_window.content_box.add(menu_button)
self.dropdown_window.set_size_request(150, -1)
self.current_dropdown = menu_item
self.dropdown_window.set_visible(True)
return False
def calculate_dropdown_position(self, button):
# Get button allocation
allocation = button.get_allocation()
print(f"Button allocation: x={allocation.x}, y={allocation.y}, width={allocation.width}, height={allocation.height}")
# Use the exact button position for perfect alignment
dropdown_x = allocation.x
dropdown_y = allocation.y + 3 # 3px gap below button
print(f"Perfect dropdown position: x={dropdown_x}, y={dropdown_y}")
return dropdown_x, dropdown_y
def hide_dropdown(self):
if self.dropdown_window:
self.dropdown_window.set_visible(False)
self.dropdown_window.destroy()
self.dropdown_window = None
self.current_dropdown = None
def on_menu_action(self, action):
# Hide dropdown after action
self.hide_dropdown()
# Handle window management actions
self.execute_menu_action(action)
def execute_menu_action(self, action):
self.action_handler.execute_action(action)
def on_window_changed(self, widget, window_class, window_title):
self.current_window_class = window_class
self.current_window_title = window_title
# Update the first button label and menu contents
if window_title and window_title.strip():
# Show the window title/app name when there's an active window
display_name = window_class if window_class else window_title
# Capitalize the first letter
display_name = display_name.capitalize() if display_name else "Hyprland"
self.app_button.get_child().set_label(display_name)
# Update the app menu contents dynamically
self.update_app_menu(display_name)
else:
# Show "Hyprland" when no active window
self.app_button.get_child().set_label("Hyprland")
self.update_app_menu("Hyprland")
# Update menu button states based on new context
self.update_menu_button_states()
def update_app_menu(self, app_name):
# Get app-specific menu contents (includes app-specific overrides if available)
self.menu_contents = get_app_specific_menu_contents(app_name)
def show_system_dropdown(self, button):
# If same system dropdown is open, hide it (toggle behavior)
if (self.current_dropdown == "System" and
self.dropdown_window and
self.dropdown_window.get_visible()):
self.hide_dropdown()
return
# Hide existing dropdown if any and show system dropdown
if self.dropdown_window:
self.dropdown_window.set_visible(False)
self.dropdown_window.destroy()
self.dropdown_window = None
# Wait a short moment to ensure the previous dropdown is completely gone
GLib.timeout_add(150, self._create_and_show_system_dropdown, button)
else:
# No existing dropdown, show immediately
self._create_and_show_system_dropdown(button)
def _create_and_show_system_dropdown(self, button):
# Calculate position first
x, y = self.calculate_dropdown_position(button)
# Create new dropdown window at the calculated position
self.dropdown_window = DropdownWindow(x=x, y=y, parent_menubar=self)
# System menu items
system_menu_items = [
"About This PC",
"---",
"Force Quit",
"---",
"Shutdown",
"Restart",
"Sleep",
"Lock"
]
# Add menu items to dropdown
for item in system_menu_items:
if item == "---":
# Add separator
separator = Box(
name="menu-separator",
size=(150, 1),
style="background-color: #444; margin: 2px 0;"
)
self.dropdown_window.content_box.add(separator)
else:
# Add menu item button
menu_button = Button(
name="dropdown-item",
child=Label(
name="dropdown-label",
label=item,
h_align="start" # Align text to the left
),
on_clicked=lambda *_, action=item: self.on_menu_action(action)
)
self.dropdown_window.content_box.add(menu_button)
# Set dropdown window size
self.dropdown_window.set_size_request(150, -1)
# Show the dropdown without animations
self.current_dropdown = "System"
# Use set_visible(True) for immediate display without animations
self.dropdown_window.set_visible(True)
# Return False to stop the timeout
return False
+169
View File
@@ -0,0 +1,169 @@
from fabric.system_tray.widgets import SystemTray, SystemTrayItem
from fabric.utils import exec_shell_command, get_relative_path
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 Gtk
from fabric.widgets.svg import Svg
from modules.panel.components.indicators import Indicators
from modules.panel.components.menubar import MenuBar
from utils.wayland import WaylandWindow as Window
original_do_update_properties = SystemTrayItem.do_update_properties
def patched_do_update_properties(self, *_):
# Try default GTK theme first
icon_name = self._item.icon_name
attention_icon_name = self._item.attention_icon_name
if self._item.status == "NeedsAttention" and attention_icon_name:
preferred_icon_name = attention_icon_name
else:
preferred_icon_name = icon_name
# Try to load from default GTK theme
if preferred_icon_name:
try:
default_theme = Gtk.IconTheme.get_default()
if default_theme.has_icon(preferred_icon_name):
pixbuf = default_theme.load_icon(
preferred_icon_name, self._icon_size, Gtk.IconLookupFlags.FORCE_SIZE
)
if pixbuf:
self._image.set_from_pixbuf(pixbuf)
# Set tooltip
tooltip = self._item.tooltip
self.set_tooltip_markup(
tooltip.description or tooltip.title or self._item.title.title()
if self._item.title
else "Unknown"
)
return
except:
pass
original_do_update_properties(self, *_)
SystemTrayItem.do_update_properties = patched_do_update_properties
class Panel(Window):
def __init__(self, **kwargs):
super().__init__(
name="bar",
layer="top",
anchor="left top right",
exclusivity="auto",
visible=False,
)
self.launcher = kwargs.get("launcher", None)
self.menubar = MenuBar()
self.imac = Button(
name="panel-button",
child=Svg(
size=24,
svg_file=get_relative_path("../../config/assets/icons/logo.svg"),
),
on_clicked=lambda *_: self.menubar.show_system_dropdown(self.imac),
)
self.notch_spot = Box(
name="notch-spot",
size=(200, 24),
h_expand=True,
v_expand=True,
children=Label(label="notch"),
)
self.tray = SystemTray(name="system-tray", spacing=4, icon_size=20)
self.tray_revealer = Revealer(
name="tray-revealer",
child=self.tray,
child_revealed=False,
transition_type="slide-left",
transition_duration=300,
)
self.chevron_button = Button(
name="panel-button",
child=Svg(
size=18,
svg_file=get_relative_path(
"../../config/assets/icons/chevron-right.svg"
),
),
on_clicked=self.toggle_tray,
)
self.indicators = Indicators()
self.search = Button(
name="panel-button",
on_clicked=lambda *_: self.search_apps(),
child=Svg(
size=20,
svg_file=get_relative_path("../../config/assets/icons/search.svg"),
),
)
self.controlcenter = Button(
name="panel-button",
child=Svg(
size=24,
svg_file=get_relative_path(
"../../config/assets/icons/control-center.svg"
),
),
)
self.children = CenterBox(
name="panel",
start_children=Box(
name="modules-left",
children=[
self.imac,
self.menubar,
],
),
# center_children=Box(
# name="modules-center",
# children=self.notch_spot,
# ),
end_children=Box(
name="modules-right",
spacing=4,
orientation="h",
children=[
self.tray_revealer,
self.chevron_button,
self.indicators,
self.search,
self.controlcenter,
DateTime(name="date-time", formatters=["%a %b %d %I:%M"]),
],
),
)
return self.show_all()
def search_apps(self):
self.launcher.show_launcher()
def toggle_tray(self, *_):
current_state = self.tray_revealer.child_revealed
self.tray_revealer.child_revealed = not current_state
if self.tray_revealer.child_revealed:
self.chevron_button.get_child().set_from_file(
get_relative_path("../../config/assets/icons/chevron-left.svg")
)
else:
self.chevron_button.get_child().set_from_file(
get_relative_path("../../config/assets/icons/chevron-right.svg")
)