Revert "ready for ignis"

This reverts commit 9d404b100f.
This commit is contained in:
sankalp committed 2025-09-01 12:39:28 +05:45
1 parent 9d404b100f
commit 158ce6590c
204 files changed
+51505

No files matched your search

@@ -0,0 +1,165 @@
"""
Enhanced System Tray Icon Handling
This module provides enhanced icon loading capabilities for system tray items,
including fallback mechanisms for file paths and common icon locations.
"""
import os
from gi.repository import GdkPixbuf, Gtk
from fabric.system_tray.widgets import SystemTrayItem
# FIX: the tooltip should show application names instead of unknown
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
# Enhanced fallback handling for file paths
if preferred_icon_name and self._try_load_icon_from_path(preferred_icon_name):
return
# Fallback to original implementation
original_do_update_properties(self, *_)
def _try_load_icon_from_path(self, icon_path):
try:
# Check if it's a file path and handle it directly
if os.path.isabs(icon_path) or "/" in icon_path:
# Try to load as SVG from the original path if it exists
if os.path.exists(icon_path):
if icon_path.lower().endswith(".svg"):
# Load SVG directly
pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(
icon_path, self._icon_size, self._icon_size
)
if pixbuf:
self._image.set_from_pixbuf(pixbuf)
self._set_tooltip()
return True
else:
# Load other image formats
pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(
icon_path, self._icon_size, self._icon_size
)
if pixbuf:
self._image.set_from_pixbuf(pixbuf)
self._set_tooltip()
return True
# If it's a file path, try to extract just the filename for theme lookup
filename = os.path.basename(icon_path)
if filename:
# Remove extension for theme lookup
name_without_ext = os.path.splitext(filename)[0]
default_theme = Gtk.IconTheme.get_default()
# Try filename without extension
if default_theme.has_icon(name_without_ext):
pixbuf = default_theme.load_icon(
name_without_ext,
self._icon_size,
Gtk.IconLookupFlags.FORCE_SIZE,
)
if pixbuf:
self._image.set_from_pixbuf(pixbuf)
self._set_tooltip()
return True
# Try full filename
if default_theme.has_icon(filename):
pixbuf = default_theme.load_icon(
filename, self._icon_size, Gtk.IconLookupFlags.FORCE_SIZE
)
if pixbuf:
self._image.set_from_pixbuf(pixbuf)
self._set_tooltip()
return True
# If it looks like a file path but doesn't exist, try common icon locations
if os.path.isabs(icon_path):
common_icon_dirs = [
"/usr/share/icons",
"/usr/share/pixmaps",
"/usr/local/share/icons",
"/usr/local/share/pixmaps",
os.path.expanduser("~/.local/share/icons"),
os.path.expanduser("~/.icons"),
]
filename = os.path.basename(icon_path)
for icon_dir in common_icon_dirs:
potential_path = os.path.join(icon_dir, filename)
if os.path.exists(potential_path):
try:
pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(
potential_path, self._icon_size, self._icon_size
)
if pixbuf:
self._image.set_from_pixbuf(pixbuf)
self._set_tooltip()
return True
except:
continue
except Exception:
pass
return False
def _set_tooltip(self):
tooltip = self._item.tooltip
self.set_tooltip_markup(
tooltip.description or tooltip.title or self._item.title.title()
if self._item.title
else "Unknown"
)
def apply_enhanced_system_tray():
# Store original method
global original_do_update_properties
original_do_update_properties = SystemTrayItem.do_update_properties
# Attach helper methods to SystemTrayItem class
SystemTrayItem._try_load_icon_from_path = _try_load_icon_from_path
SystemTrayItem._set_tooltip = _set_tooltip
# Replace the do_update_properties method
SystemTrayItem.do_update_properties = patched_do_update_properties
# Store reference to original method
original_do_update_properties = None
+477
View File
@@ -0,0 +1,477 @@
from fabric.bluetooth import BluetoothClient
from fabric.utils import get_relative_path
from fabric.widgets.box import Box
from fabric.widgets.button import Button
from fabric.widgets.label import Label
from fabric.widgets.svg import Svg
from modules.controlcenter.battery import BatteryControl
from modules.controlcenter.bluetooth import BluetoothConnections
from modules.controlcenter.wifi import WifiConnections
from services.battery import Battery
from services.network import NetworkClient
from utils.roam import modus_service
from utils.functions import get_wifi_icon_for_strength, get_wifi_connecting_icon
from widgets.mousecapture import DropDownMouseCapture
from widgets.wayland import WaylandWindow as Window
class BluetoothIndicator(Box):
def __init__(self, show_window=True, **kwargs):
super().__init__(name="bluetooth-indicator", orientation="h", **kwargs)
self.show_window = show_window
self.bluetooth = BluetoothClient()
self.bt_icon = Svg(
name="bt-icon",
size=22,
svg_file=get_relative_path(
"../../../config/assets/icons/applets/bluetooth-clear.svg"
),
)
self.bt_button = Button(
name="bt-button", child=self.bt_icon, on_clicked=self.on_bluetooth_clicked
)
self.add(self.bt_button)
# Create Bluetooth control center widget only if show_window is True
if self.show_window:
self.bluetooth_window = Window(
layer="overlay",
title="modus",
anchor="top right",
margin="2px 10px 0px 0px",
exclusivity="auto",
keyboard_mode="on-demand",
name="bluetooth-control-window",
visible=False,
)
self.bluetooth_widget = BluetoothConnections(self, show_back_button=False)
self.bluetooth_window.children = [self.bluetooth_widget]
# Create mouse capture for Bluetooth widget
self.bluetooth_mousecapture = DropDownMouseCapture(
layer="top", child_window=self.bluetooth_window
)
else:
self.bluetooth_window = None
self.bluetooth_widget = None
self.bluetooth_mousecapture = None
modus_service.connect("bluetooth-changed", self.on_bluetooth_changed)
self.bluetooth.connect("changed", self.on_bluetooth_direct_changed)
self.bluetooth.connect("device-added", self.on_device_added)
self.bluetooth.connect("device-removed", self.on_device_removed)
self.update_modus_service_bluetooth_state()
self.update_state()
def update_state(self):
if not self.bluetooth.enabled:
self.bt_icon.set_from_file(
get_relative_path(
"../../../config/assets/icons/applets/bluetooth-off-clear.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/applets/bluetooth-clear.svg"
)
)
if len(connected_devices) >= 1:
self.bt_icon.set_from_file(
get_relative_path(
"../../../config/assets/icons/applets/bluetooth-paired.svg"
)
)
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/applets/bluetooth-clear.svg"
)
)
tooltip = "No devices connected"
self.bt_button.set_tooltip_text(tooltip)
def on_bluetooth_changed(self, service, new_bluetooth_state):
self.update_state()
def on_bluetooth_direct_changed(self, *args):
self.update_modus_service_bluetooth_state()
self.update_state()
def on_device_added(self, _, address):
self.update_modus_service_bluetooth_state()
self.update_state()
def on_device_removed(self, _, address):
self.update_modus_service_bluetooth_state()
self.update_state()
def update_modus_service_bluetooth_state(self):
if not self.bluetooth.enabled:
bluetooth_state = "disabled"
else:
connected_devices = self.bluetooth.connected_devices
if connected_devices:
if len(connected_devices) == 1:
device = connected_devices[0]
bluetooth_state = f"connected:{device.alias}"
if (
hasattr(device, "battery_percentage")
and device.battery_percentage > 0
):
bluetooth_state += f":{device.battery_percentage:.0f}%"
else:
bluetooth_state = f"connected:{len(connected_devices)}_devices"
else:
bluetooth_state = "enabled"
modus_service.bluetooth = bluetooth_state
def on_bluetooth_clicked(self, *args):
"""Handle Bluetooth indicator click"""
if self.show_window and self.bluetooth_mousecapture:
self.bluetooth_mousecapture.toggle_mousecapture()
def close_bluetooth(self, *args):
"""Close Bluetooth control center"""
if self.show_window and self.bluetooth_mousecapture:
self.bluetooth_mousecapture.hide_child_window()
def hide_controlcenter(self, *args):
"""Hide Bluetooth control center"""
if self.show_window and self.bluetooth_mousecapture:
self.bluetooth_mousecapture.hide_child_window()
class NetworkIndicator(Box):
def __init__(self, show_window=True, **kwargs):
super().__init__(name="network-indicator", orientation="h", **kwargs)
self.show_window = show_window
self.network_service = NetworkClient()
self.network_icon = Svg(
name="network-icon",
size=22,
svg_file=get_relative_path(
"../../../config/assets/icons/applets/wifi-clear.svg"
),
)
self.network_button = Button(
name="network-button",
child=self.network_icon,
on_clicked=self.on_wifi_clicked,
)
self.add(self.network_button)
# Create WiFi control center widget only if show_window is True
if self.show_window:
self.wifi_window = Window(
layer="overlay",
title="modus",
anchor="top right",
margin="2px 10px 0px 0px",
exclusivity="auto",
keyboard_mode="on-demand",
name="wifi-control-window",
visible=False,
)
self.wifi_widget = WifiConnections(self, show_back_button=False)
self.wifi_window.children = [self.wifi_widget]
# Create mouse capture for WiFi widget
self.wifi_mousecapture = DropDownMouseCapture(
layer="top", child_window=self.wifi_window
)
else:
self.wifi_window = None
self.wifi_widget = None
self.wifi_mousecapture = None
modus_service.connect("wlan-changed", self.on_wlan_changed)
self.network_service.connect("wifi-device-added", self.on_wifi_device_added)
self.network_service.connect(
"ethernet-device-added", self.on_ethernet_device_added
)
self.network_service.connect("changed", self.on_network_changed)
self.update_modus_service_wlan_state()
self.update_state()
def on_wlan_changed(self, service, new_wlan_state):
self.update_state()
def on_wifi_device_added(self, *args):
"""Called when WiFi device is added"""
if self.network_service.wifi_device:
self.network_service.wifi_device.connect(
"changed", self.on_network_direct_changed
)
self.update_modus_service_wlan_state()
self.update_state()
def on_ethernet_device_added(self, *args):
"""Called when Ethernet device is added"""
if self.network_service.ethernet_device:
self.network_service.ethernet_device.connect(
"changed", self.on_network_direct_changed
)
self.update_modus_service_wlan_state()
self.update_state()
def on_network_direct_changed(self, *args):
self.update_modus_service_wlan_state()
self.update_state()
def on_network_changed(self, *args):
self.update_modus_service_wlan_state()
self.update_state()
def update_modus_service_wlan_state(self):
wlan_state = "disconnected"
# Check WiFi first (prioritize WiFi over Ethernet)
if self.network_service.wifi_device:
wifi = self.network_service.wifi_device
if not wifi.wireless_enabled:
wlan_state = "disabled"
elif wifi.active_access_point:
ap = wifi.active_access_point
wlan_state = f"connected:{ap.ssid}"
if ap.strength >= 0:
wlan_state += f":{ap.strength}%"
else:
wlan_state = "enabled"
# Check Ethernet if WiFi is not connected
elif self.network_service.ethernet_device:
ethernet = self.network_service.ethernet_device
if ethernet.internet == "activated":
wlan_state = "ethernet:connected"
if hasattr(ethernet, "speed") and ethernet.speed:
wlan_state += f":{ethernet.speed}"
elif ethernet.internet == "activating":
wlan_state = "ethernet:connecting"
else:
wlan_state = "ethernet:disconnected"
modus_service.wlan = wlan_state
def update_state(self):
tooltip = "No network connection"
icon_file = "wifi-off-clear.svg"
# Check WiFi first (prioritize WiFi over Ethernet)
if self.network_service.wifi_device:
wifi = self.network_service.wifi_device
if not wifi.wireless_enabled:
icon_file = "wifi-off-clear.svg"
tooltip = "WiFi disabled"
elif wifi.active_access_point:
ap = wifi.active_access_point
# Use dynamic WiFi icon based on signal strength
wifi_icon_path = get_wifi_icon_for_strength(ap.strength)
self.network_icon.set_from_file(wifi_icon_path)
tooltip = f"Connected to {ap.ssid}"
if ap.strength >= 0:
tooltip += f" ({ap.strength}%)"
self.network_button.set_tooltip_text(tooltip)
return # Early return to avoid setting icon again
else:
icon_file = "wifi-off-clear.svg"
tooltip = "WiFi disconnected"
# Check Ethernet if WiFi is not connected
elif self.network_service.ethernet_device:
ethernet = self.network_service.ethernet_device
if ethernet.internet == "activated":
icon_file = "network-wired.svg"
tooltip = "Ethernet connected"
if hasattr(ethernet, "speed") and ethernet.speed:
tooltip += f" ({ethernet.speed})"
elif ethernet.internet == "activating":
icon_file = "network-wired.svg"
tooltip = "Ethernet connecting..."
else:
icon_file = "network-wired-offline.svg"
tooltip = "Ethernet disconnected"
self.network_icon.set_from_file(
get_relative_path(f"../../../config/assets/icons/applets/{icon_file}")
)
self.network_button.set_tooltip_text(tooltip)
def on_wifi_clicked(self, *args):
"""Handle WiFi indicator click"""
if self.show_window and self.wifi_mousecapture:
self.wifi_mousecapture.toggle_mousecapture()
def close_wifi(self, *args):
"""Close WiFi control center"""
if self.show_window and self.wifi_mousecapture:
self.wifi_mousecapture.hide_child_window()
def hide_controlcenter(self, *args):
"""Hide WiFi control center"""
if self.show_window and self.wifi_mousecapture:
self.wifi_mousecapture.hide_child_window()
class BatteryIndicator(Box):
def __init__(self, show_window=True, **kwargs):
super().__init__(name="battery-indicator", orientation="h", **kwargs)
self.show_window = show_window
self.battery_service = Battery()
self.battery_icon = Svg(
name="battery-icon",
size=23,
svg_file=get_relative_path(
"../../../config/assets/icons/battery/battery-100.svg"
),
)
self.battery_button = Button(
name="battery-button",
child=self.battery_icon,
on_clicked=self.on_battery_clicked,
)
self.battery_label = Label(name="battery-label", label="--- %")
self.add(self.battery_label)
self.add(self.battery_button)
# Create Battery control center widget only if show_window is True
if self.show_window:
self.battery_window = Window(
layer="top",
title="modus",
anchor="top right",
margin="2px 10px 0px 0px",
exclusivity="auto",
keyboard_mode="on-demand",
name="battery-control-window",
visible=False,
)
self.battery_widget = BatteryControl(self, show_back_button=False)
self.battery_window.children = [self.battery_widget]
# Create mouse capture for Battery widget
self.battery_mousecapture = DropDownMouseCapture(
layer="top", child_window=self.battery_window
)
else:
self.battery_window = None
self.battery_widget = None
self.battery_mousecapture = None
modus_service.connect("battery-changed", self.on_battery_changed)
self.battery_service.connect("changed", self.on_battery_direct_changed)
self.update_modus_service_battery_state()
self.update_state()
def on_battery_changed(self, service, new_battery_state):
self.update_state()
def on_battery_direct_changed(self, *args):
self.update_modus_service_battery_state()
self.update_state()
def update_modus_service_battery_state(self):
if not self.battery_service.is_present:
battery_state = "not_present"
else:
percentage = self.battery_service.percentage
state = self.battery_service.state.lower()
battery_state = f"{state}:{percentage}%"
if state == "discharging":
time_to_empty = self.battery_service.time_to_empty
if time_to_empty != "N/A":
battery_state += f":{time_to_empty}"
elif state == "charging":
time_to_full = self.battery_service.time_to_full
if time_to_full != "N/A":
battery_state += f":{time_to_full}"
modus_service.battery = battery_state
def get_battery_tooltip(self, percentage, state):
tooltip = f"Battery: {percentage}%"
if state == "CHARGING":
tooltip += " (Charging)"
time_to_full = self.battery_service.time_to_full
if time_to_full != "N/A":
tooltip += f" - {time_to_full} until full"
elif state == "DISCHARGING":
time_to_empty = self.battery_service.time_to_empty
if time_to_empty != "N/A":
tooltip += f" - {time_to_empty} remaining"
elif state == "FULLY_CHARGED":
tooltip += " (Fully charged)"
return tooltip
def update_state(self):
if not self.battery_service.is_present:
# Hide the entire battery component when no battery is present
self.set_visible(False)
return
else:
# Show the battery component when battery is present
self.set_visible(True)
percentage = self.battery_service.percentage
state = self.battery_service.state
is_charging = state in ["CHARGING", "FULLY_CHARGED"]
icon_file = Battery.get_battery_icon_file(
percentage, is_charging, base_path="../../../config/assets/icons/"
)
tooltip = self.get_battery_tooltip(percentage, state)
percentage_text = f"{percentage}%"
# Update icon, tooltip, and percentage label
self.battery_icon.set_from_file(get_relative_path(icon_file))
self.battery_button.set_tooltip_text(tooltip)
self.battery_label.set_label(percentage_text)
def on_battery_clicked(self, *args):
"""Handle Battery indicator click"""
if self.show_window and self.battery_mousecapture:
self.battery_mousecapture.toggle_mousecapture()
def close_battery(self, *args):
"""Close Battery control center"""
if self.show_window and self.battery_mousecapture:
self.battery_mousecapture.hide_child_window()
def hide_controlcenter(self, *args):
"""Hide Battery control center"""
if self.show_window and self.battery_mousecapture:
self.battery_mousecapture.hide_child_window()
+411
View File
@@ -0,0 +1,411 @@
import json
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, AboutApp
from utils.roam import modus_service
from widgets.dropdown import ModusDropdown, dropdown_divider
from widgets.mousecapture import DropDownMouseCapture
from utils.app_name_resolver import format_window
def show_about_app():
"""Show about dialog for current active application"""
try:
# Use modus_service's Hyprland connection to get current window info
wmclass = ""
title = ""
if (
hasattr(modus_service, "_hyprland_connection")
and modus_service._hyprland_connection
):
window_data = modus_service._hyprland_connection.send_command(
"j/activewindow"
).reply
if window_data:
window_info = json.loads(window_data.decode("utf-8"))
wmclass = window_info.get("class", "")
title = window_info.get("title", "")
# Don't show about dialog if there's no active window (Finder state)
if not wmclass and not title:
return
app_name = modus_service.current_active_app_name or "Finder"
# Don't show about dialog for Finder
if app_name == "Finder":
return
about_window = AboutApp(app_name=app_name, wmclass=wmclass)
about_window.toggle(None)
except Exception:
# Fallback: only show if we have a real app name
app_name = modus_service.current_active_app_name or ""
if app_name and app_name != "Finder":
about_window = AboutApp(app_name=app_name, wmclass="")
about_window.toggle(None)
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}",
on_clicked=lambda _: show_about_app(),
)
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}"
),
)
# Connect to active app name changes to update the title button
modus_service.connect("current-active-app-name-changed", self._on_active_app_changed)
self.global_menu_button_title = Button(
child=ActiveWindow(
formatter=FormattedString(
"{ format_window(win_title, win_class) }",
format_window=format_window,
)
),
name="global-title-button",
style_classes="button",
on_clicked=self._on_title_button_clicked,
)
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 _on_title_button_clicked(self, _):
"""Handle title button click - only show dropdown if there's an active window"""
try:
# Use modus_service's Hyprland connection to get current window info
if (
hasattr(modus_service, "_hyprland_connection")
and modus_service._hyprland_connection
):
window_data = modus_service._hyprland_connection.send_command(
"j/activewindow"
).reply
if window_data:
window_info = json.loads(window_data.decode("utf-8"))
wmclass = window_info.get("class", "")
title = window_info.get("title", "")
# Only show dropdown if there's an active window (not Finder)
if wmclass or title:
self.global_menu_title.toggle_mousecapture()
return
# Fallback: check if current_active_app_name is not "Finder"
if (
modus_service.current_active_app_name
and modus_service.current_active_app_name != "Finder"
):
self.global_menu_title.toggle_mousecapture()
except Exception:
# If we can't get window info, don't show dropdown
pass
def _on_active_app_changed(self, _, value):
"""Handle active app name changes"""
# Update the "About" menu item label
self.global_title_menu_about.set_property("label", f"About {value}")
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)
# Add all the menu buttons to the menubar
self.children = [
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)
@@ -0,0 +1,236 @@
import os
import subprocess
import time
from fabric.utils import get_relative_path
from fabric.widgets.box import Box
from fabric.widgets.button import Button
from fabric.widgets.label import Label
from fabric.widgets.svg import Svg
from gi.repository import GLib
class RecordingIndicator(Button):
def __init__(self, **kwargs):
super().__init__(name="panel-button", visible=True, **kwargs)
self.script_path = get_relative_path("../../../scripts/screen-capture.sh")
self.recording_start_time = None
self.last_process_check = 0
self.process_check_interval = 1.0
self.timer_update_interval = 1000
self.status_check_interval = 2000
self.timer_timeout_id = None
self.status_timeout_id = None
self.recording_icon = Svg(
name="indicators-icon",
size=24,
svg_file=get_relative_path(
"../../../config/assets/icons/misc/media-record.svg"
),
)
self.time_label = Label(
name="recording-time-label",
markup="00:00",
max_width_chars=5,
ellipsize="none",
)
self.recording_box = Box(
orientation="h",
spacing=2,
children=[self.recording_icon, self.time_label],
size=(80, -1),
)
self.add(self.recording_box)
self.connect("clicked", self.on_stop_recording)
self.connect("button-press-event", self.on_button_press)
self.hide()
GLib.timeout_add(100, self._delayed_init)
def on_button_press(self, *args):
GLib.timeout_add(100, lambda: self.remove_style_class("pressed") or False)
return False
def is_recorder_running(self):
# add more process names if needed
recorder_processes = ["wf-recorder", "gpu-screen-recorder"]
try:
for proc in recorder_processes:
result = subprocess.run(
["pgrep", "-x", proc],
capture_output=True,
text=True,
timeout=1,
)
if result.returncode == 0:
return True # Found a running recorder process
return False # None found running
except Exception:
return False
def check_recording_status(self):
current_time = time.time()
self.last_process_check = current_time
try:
is_recording = self.is_recorder_running()
if is_recording:
if not self.get_visible():
self.set_visible(True)
if self.timer_timeout_id is None:
self.timer_timeout_id = GLib.timeout_add(
self.timer_update_interval, self.update_timer_display
)
if self.recording_start_time is None:
self.recording_start_time = self.get_recording_start_time()
self.update_timer_display()
else:
if self.get_visible():
self.set_visible(False)
self.cleanup_recording_state()
except Exception as e:
print(f"[DEBUG] Error checking recording status: {e}")
if self.get_visible():
self.set_visible(False)
self.cleanup_recording_state()
return True
def update_timer_display(self):
if not self.get_visible() or self.recording_start_time is None:
return False
try:
elapsed_seconds = int(time.time() - self.recording_start_time)
minutes = elapsed_seconds // 60
seconds = elapsed_seconds % 60
time_text = f"{minutes:02d}:{seconds:02d}"
self.time_label.set_markup(time_text)
self.set_tooltip_text(
f"Recording in progress ({time_text}) - Click to stop"
)
return True
except Exception as e:
print(f"[DEBUG] Error updating timer display: {e}")
return False
def cleanup_recording_state(self):
self.recording_start_time = None
if self.timer_timeout_id:
GLib.source_remove(self.timer_timeout_id)
self.timer_timeout_id = None
def get_recording_start_time(self):
wf_file = "/tmp/recording_start_time.txt"
gpu_file = "/tmp/gpu_recording_start_time.txt"
def read_timestamp(path):
try:
with open(path, "r") as f:
content = f.read().strip()
if content:
t = float(content)
if abs(t - time.time()) <= 3600:
return t
return os.path.getmtime(path)
except (OSError, ValueError):
return None
if os.path.exists(wf_file):
t = read_timestamp(wf_file)
if t:
print("[DEBUG] Using wf-recorder start time")
return t
if os.path.exists(gpu_file):
t = read_timestamp(gpu_file)
if t:
print("[DEBUG] Using gpu-screen-recorder start time")
return t
print("[DEBUG] No start time file found, using current time")
return time.time()
def on_stop_recording(self, *args):
try:
self.set_visible(False)
self.cleanup_recording_state()
def send_stop_command():
try:
subprocess.Popen(
[self.script_path, "record", "stop"],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
except Exception as e:
print(f"[DEBUG] Error sending stop command: {e}")
GLib.idle_add(send_stop_command)
GLib.timeout_add(500, self._verify_recording_stopped)
GLib.timeout_add(1500, self._verify_recording_stopped)
GLib.timeout_add(3000, self._verify_recording_stopped)
except Exception:
self.set_visible(False)
self.cleanup_recording_state()
def _verify_recording_stopped(self):
try:
if self.is_recorder_running():
if self.recording_start_time is None:
self.recording_start_time = self.get_recording_start_time()
if self.recording_start_time:
self.set_visible(True)
self.update_timer_display()
if self.timer_timeout_id is None:
self.timer_timeout_id = GLib.timeout_add(
self.timer_update_interval, self.update_timer_display
)
else:
self.set_visible(False)
self.cleanup_recording_state()
except Exception:
self.set_visible(False)
self.cleanup_recording_state()
return False
def _delayed_init(self):
try:
self.check_recording_status()
self.status_timeout_id = GLib.timeout_add(
self.status_check_interval, self.check_recording_status
)
except Exception as e:
print(f"[DEBUG] Error in delayed recording indicator init: {e}")
return False
def destroy(self):
if self.timer_timeout_id:
GLib.source_remove(self.timer_timeout_id)
self.timer_timeout_id = None
if self.status_timeout_id:
GLib.source_remove(self.status_timeout_id)
self.status_timeout_id = None
super().destroy()
+44
View File
@@ -0,0 +1,44 @@
from fabric.hyprland.widgets import HyprlandWorkspaces, WorkspaceButton
from fabric.widgets.box import Box
import config.data as data
from utils.functions import is_special_workspace_id
# TODO: Support Multi Monitor
class WorkspaceIndicator(Box):
def __init__(self, **kwargs):
super().__init__(
name="workspace-indicator", orientation="h", spacing=4, **kwargs
)
# Create workspace widget with custom button factory if filtering is enabled
if data.HIDE_SPECIAL_WORKSPACE:
self.workspaces = HyprlandWorkspaces(
name="workspaces",
spacing=4,
buttons_factory=self._create_filtered_button,
)
else:
self.workspaces = HyprlandWorkspaces(
name="workspaces",
spacing=4,
buttons_factory=lambda ws_id: WorkspaceButton(
id=ws_id, label=str(ws_id)
),
)
self.add(self.workspaces)
self.show_all()
def _is_special_workspace_id(self, ws_id):
return is_special_workspace_id(ws_id)
def _create_filtered_button(self, ws_id):
if data.HIDE_SPECIAL_WORKSPACE and self._is_special_workspace_id(ws_id):
# Return None or an empty widget to hide special workspaces
return None
return WorkspaceButton(id=ws_id, label=str(ws_id))