refactor(src/window): migrate control center windows to AppletWindow and simplify UI

Replace Window base with AppletWindow for consistency, switch to FlatScale, and streamline mouse capture handling.
This commit is contained in:
sankalp committed 2026-06-17 01:45:43 +05:45
1 parent b8d3e74afd
commit f78534dce0
11 files changed
+168 -199

No files matched your search

+7 -9
View File
@@ -1,7 +1,7 @@
import subprocess
from fabric.bluetooth import BluetoothClient, BluetoothDevice
from fabric.utils import Gdk, GLib, Gtk, exec_shell_command, logger
from fabric.utils import Gdk, GLib, exec_shell_command, logger
from fabric.widgets.box import Box
from fabric.widgets.button import Button
from fabric.widgets.centerbox import CenterBox
@@ -12,6 +12,7 @@ from fabric.widgets.scrolledwindow import ScrolledWindow
from fabric.widgets.separator import Separator
from utils.utils import svg_file
from shared.widgets.smooth_switch import SmoothSwitch
def get_battery_icon_file(
@@ -246,14 +247,11 @@ class BluetoothConnections(Box):
children=title_children,
)
self.toggle_button = Gtk.Switch(visible=True, name="toggle-button")
# Safely set initial state
self.toggle_button.set_active(self.client.enabled)
self.toggle_button.connect(
"notify::active",
lambda *_: set_bluetooth_enabled_with_fallback(
self.client, self.toggle_button.get_active()
self.toggle_button = SmoothSwitch(
name="toggle-button",
active=self.client.enabled,
on_user_toggle=lambda active: set_bluetooth_enabled_with_fallback(
self.client, active
),
)
@@ -1437,9 +1437,6 @@ class ExpandedPlayer(Window):
super().destroy()
def _init_mousecapture(self, mousecapture):
self._mousecapture_parent = mousecapture
def hide_controlcenter(self, *_):
# self._mousecapture_parent.toggle_mousecapture()
self.set_visible(False)
+17 -19
View File
@@ -5,8 +5,7 @@ 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 fabric.widgets.scale import Scale
from fabric.widgets.wayland import WaylandWindow as Window
from shared.widgets.flat_scale import FlatScale
from services.brightness import Brightness
from services.network import NetworkClient
@@ -22,18 +21,21 @@ from window.controlcenter.per_app_volume import PerAppVolumeControl
from window.controlcenter.player import PlayerBoxStack, get_shared_mpris_manager
from window.controlcenter.wifi import WifiConnections
brightness_service = Brightness.get_initial()
from shared.window.applet_window import AppletWindow
brightness_service = Brightness()
class ModusControlCenter(Window):
def __init__(self, **kwargs):
class ModusControlCenter(AppletWindow):
def __init__(self, parent=None, pointing_to=None, **kwargs):
super().__init__(
parent=parent,
pointing_to=pointing_to,
layer="top",
title="modus",
anchor="top right",
margin="2px 10px 0px 0px",
exclusivity="auto",
keyboard_mode="on-demand",
name="control-center-menu",
visible=False,
**kwargs,
@@ -98,11 +100,11 @@ class ModusControlCenter(Window):
ellipsization="end",
h_align="start",
)
self.volume_scale = Scale(
self.volume_scale = FlatScale(
value=volume,
min_value=0,
max_value=100,
increments=(5, 5),
step=5,
name="volume-widget-slider",
size=30,
h_expand=True,
@@ -115,11 +117,11 @@ class ModusControlCenter(Window):
else 50
)
self.brightness_scale = Scale(
self.brightness_scale = FlatScale(
value=brightness_percentage,
min_value=0,
max_value=100,
increments=(5, 5),
step=5,
name="brightness-widget-slider",
size=30,
h_expand=True,
@@ -555,7 +557,7 @@ class ModusControlCenter(Window):
# Connect brightness controls if brightness service is available
if brightness_service.max_screen > 0:
self.brightness_scale.connect("change-value", self.set_brightness)
self.brightness_scale.connect("value-changed", self.set_brightness)
self.brightness_scale.connect("scroll-event", self.on_brightness_scroll)
self._signal_connections.append(
(
@@ -565,7 +567,7 @@ class ModusControlCenter(Window):
)
# Connect volume scale signals
self.volume_scale.connect("change-value", self.set_volume)
self.volume_scale.connect("value-changed", self.set_volume)
self.volume_scale.connect("scroll-event", self.on_volume_scroll)
# Mark signals as connected
@@ -762,14 +764,14 @@ class ModusControlCenter(Window):
except Exception as e:
logger.warning(f"Failed to toggle caffeine: {e}")
def set_volume(self, _, __, volume):
def set_volume(self, _, volume):
if not self._signals_connected:
return
self._updating_volume = True
audio_service.speaker.volume = round(volume)
self._updating_volume = False
def set_brightness(self, _, __, brightness):
def set_brightness(self, _, brightness):
if not self._signals_connected:
return
self._updating_brightness = True
@@ -1023,9 +1025,6 @@ class ModusControlCenter(Window):
)
self.focus_status_label.set_label("On" if self.focus_mode else "Off")
def _init_mousecapture(self, mousecapture):
self._mousecapture_parent = mousecapture
def _disconnect_all_signals(self):
"""Disconnect all signal connections to prevent memory leaks"""
try:
@@ -1206,8 +1205,7 @@ class ModusControlCenter(Window):
self._disconnect_signals_when_hidden()
# Hide the control center
if hasattr(self, "_mousecapture_parent"):
self._mousecapture_parent.toggle_mousecapture()
self.set_visible(False)
self.set_visible(False)
except Exception as e:
+5 -5
View File
@@ -3,7 +3,7 @@ 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 fabric.widgets.scale import Scale
from shared.widgets.flat_scale import FlatScale
from fabric.widgets.scrolledwindow import ScrolledWindow
from fabric.widgets.separator import Separator
@@ -249,11 +249,11 @@ class PerAppVolumeControl(Box):
)
# Volume scale (smaller and horizontal)
volume_scale = Scale(
volume_scale = FlatScale(
value=volume_percent,
min_value=0,
max_value=max_vol,
increments=(1, 5),
step=5,
name="compact-volume-slider",
size=20,
h_expand=True,
@@ -261,8 +261,8 @@ class PerAppVolumeControl(Box):
# Connect volume change handler
volume_scale.connect(
"change-value",
lambda scale, scroll_type, value, app=app: self._set_app_volume(app, value),
"value-changed",
lambda scale, value, app=app: self._set_app_volume(app, value),
)
# Create horizontal compact layout
+8 -3
View File
@@ -1,6 +1,6 @@
import subprocess
from fabric.utils import Gdk, GLib, Gtk
from fabric.utils import Gdk, GLib
from fabric.widgets.box import Box
from fabric.widgets.button import Button
from fabric.widgets.centerbox import CenterBox
@@ -13,6 +13,7 @@ from fabric.widgets.separator import Separator
from services.network import NetworkClient
from utils.functions import get_wifi_connecting_icon, get_wifi_icon_for_strength
from utils.utils import svg_file
from shared.widgets.smooth_switch import SmoothSwitch
from shared.dialogs.wifi_password_dialog import WiFiPasswordDialog
from gi.repository import NM
@@ -299,7 +300,12 @@ class WifiConnections(Box):
children=title_children,
)
self.toggle_button = Gtk.Switch(visible=True, name="toggle-button")
self.toggle_button = SmoothSwitch(
name="toggle-button",
on_user_toggle=lambda active: self.on_toggle_changed(
self.toggle_button, active
),
)
# Create Known Network section
self.known_networks_label = Label(
@@ -393,7 +399,6 @@ class WifiConnections(Box):
if self.wifi_service:
# Set up WiFi toggle
self.toggle_button.set_active(self.wifi_service.wireless_enabled)
self.toggle_button.connect("notify::active", self.on_toggle_changed)
# Connect to WiFi service signals — track IDs for cleanup
self._signal_ids.append(
+11 -14
View File
@@ -9,11 +9,11 @@ from fabric.widgets.image import Image
from fabric.widgets.label import Label
from fabric.widgets.revealer import Revealer
from fabric.widgets.scrolledwindow import ScrolledWindow
from fabric.widgets.wayland import WaylandWindow as Window
import shared.data as data
from services.modus import notification_service
from shared.widgets.clipping_box import ClippingBox
from shared.window.applet_window import AppletWindow
from shared.widgets.custom_image import CustomImage
from utils.functions import escape_markup_text
from window.notification.notification import (
@@ -591,13 +591,14 @@ class NotificationCenterWidget(NotificationWidget):
return False
class NotificationCenter(Window):
def __init__(self):
class NotificationCenter(AppletWindow):
def __init__(self, parent=None, pointing_to=None):
super().__init__(
parent=parent,
pointing_to=pointing_to,
layer="overlay",
anchor="top right",
visible=False,
keyboard_mode="on-demand",
title="modus",
)
@@ -840,8 +841,8 @@ class NotificationCenter(Window):
self.scrolled.set_visible(current_count > 0)
# Auto-close notification center when no notifications remain
if current_count == 0 and hasattr(self, "mousecapture"):
self.mousecapture.hide_child_window()
if current_count == 0:
self.hide()
def clear_all_notifications(self, *_):
# Clear all groups
@@ -851,18 +852,14 @@ class NotificationCenter(Window):
# Clear all remaining cached notification images AND icons when clear all is clicked
cleanup_all_notification_caches() # Clear ALL caches (icons + images)
notification_service.clear_all_cached_notifications()
if hasattr(self, "mousecapture"):
self.mousecapture.hide_child_window()
self.hide()
def _on_escape_pressed(self, *_):
if hasattr(self, "mousecapture"):
self.mousecapture.hide_child_window()
self.hide()
def _init_mousecapture(self, mousecapture):
self.mousecapture = mousecapture
def _set_mousecapture(self, visible):
def set_visible(self, visible: bool):
"""Control notification center visibility with slide-left animation"""
super().set_visible(visible)
if visible:
self.main_revealer.set_reveal_child(True)
else:
+28 -41
View File
@@ -7,7 +7,6 @@ from fabric.widgets.label import Label
from shared.dialogs.about import AboutApp, get_about_window
from shared.window.dropdown import ModusDropdown, dropdown_divider, dropdowns
from shared.window.mousecapture import DropDownMouseCapture
from utils.app_name_resolver import format_window
from utils.roam import modus_service
from utils.utils import setup_cursor_hover
@@ -41,8 +40,7 @@ def create_dropdown_with_capture(dropdown_id, parent, dropdown_children, layer="
parent=parent,
dropdown_children=dropdown_children,
)
mouse_capture = DropDownMouseCapture(layer=layer, child_window=dropdown)
return mouse_capture
return dropdown
def manage_button_style_classes(buttons, active_button=None, style_class="active"):
@@ -72,7 +70,7 @@ def on_click_subthread(button, on_clicked, on_click):
for dropdown in dropdowns:
if dropdown.is_visible():
dropdown.hide_via_mousecapture()
dropdown.hide()
break
@@ -137,15 +135,13 @@ class GlobalMenuDropdowns:
self.parent = parent
self.system_dropdown = SystemDropdown(parent=parent)
self.menu_button_dropdown = DropDownMouseCapture(
layer="top", child_window=self.system_dropdown
)
self.menu_button_dropdown = self.system_dropdown
self.menu_button = Button(
label="Modus",
name="global-menu",
on_clicked=lambda _: self.menu_button_dropdown.toggle_mousecapture(),
on_clicked=lambda _: self.menu_button_dropdown.toggle(),
)
self.menu_button_dropdown.child_window.set_pointing_to(self.menu_button)
self.menu_button_dropdown.set_pointing_to(self.menu_button)
self.global_title_menu_about = dropdown_option(
f"About {modus_service.current_active_app_name}",
@@ -162,8 +158,8 @@ class GlobalMenuDropdowns:
parent,
[
dropdown_option(
"Enter Full Screen",
on_click="hyprctl dispatch 'hl.dsp.fullscreen()'",
"Fullscreen",
on_click="hyprctl dispatch 'hl.dsp.window.fullscreen()'",
),
],
)
@@ -183,35 +179,35 @@ class GlobalMenuDropdowns:
),
dropdown_divider("---------------------"),
dropdown_option(
"Move Window to Left",
on_click="hyprctl dispatch 'hl.dsp.movewindow([[l]])'",
"Left",
on_click="hyprctl dispatch 'hl.dsp.window.move([[l]])'",
),
dropdown_option(
"Move Window to Right",
on_click="hyprctl dispatch 'hl.dsp.movewindow([[r]])'",
"Right",
on_click="hyprctl dispatch 'hl.dsp.window.move([[r]])'",
),
dropdown_option(
"Cycle Through Windows",
on_click="hyprctl dispatch 'hl.dsp.cyclenext()'",
"Cycle Next",
on_click="hyprctl dispatch 'hl.dsp.window.cycle_next()'",
),
dropdown_divider("---------------------"),
dropdown_option(
"Float", on_click="hyprctl dispatch 'hl.dsp.togglefloating()'"
"Float", on_click="hyprctl dispatch 'hl.dsp.window.float()'"
),
dropdown_option(
"Quit", on_click="hyprctl dispatch 'hl.dsp.killactive()'"
"Quit", on_click="hyprctl dispatch 'hl.dsp.window.close()'"
),
dropdown_option(
"Pseudo", on_click="hyprctl dispatch 'hl.dsp.pseudo()'"
"Pseudo", on_click="hyprctl dispatch 'hl.dsp.window.pseudo()'"
),
dropdown_option(
"Toggle Split", on_click="hyprctl dispatch 'hl.dsp.togglesplit()'"
),
dropdown_option(
"Center", on_click="hyprctl dispatch 'hl.dsp.centerwindow()'"
"Center", on_click="hyprctl dispatch 'hl.dsp.window.center()'"
),
dropdown_option(
"Group", on_click="hyprctl dispatch 'hl.dsp.togglegroup()'"
"Group", on_click="hyprctl dispatch 'hl.dsp.group.toggle()'"
),
dropdown_option(
"Pin",
@@ -252,31 +248,24 @@ class GlobalMenuDropdowns:
on_clicked=self._on_title_button_clicked,
)
self.global_menu_title.child_window.set_pointing_to(
self.global_menu_button_title
)
self.global_menu_title.set_pointing_to(self.global_menu_button_title)
# File, Edit and Go buttons are placeholders - no dropdowns implemented yet
self.global_menu_button_file = create_menu_button("File")
self.global_menu_button_edit = create_menu_button("Edit")
self.global_menu_button_go = create_menu_button("Go")
self.global_menu_button_view = create_menu_button(
"View",
lambda _: self.global_menu_view.toggle_mousecapture(),
"View", lambda _: self.global_menu_view.toggle()
)
self.global_menu_view.child_window.set_pointing_to(self.global_menu_button_view)
self.global_menu_view.set_pointing_to(self.global_menu_button_view)
self.global_menu_button_window = create_menu_button(
"Window",
lambda _: self.global_menu_window.toggle_mousecapture(),
)
self.global_menu_window.child_window.set_pointing_to(
self.global_menu_button_window
"Window", lambda _: self.global_menu_window.toggle()
)
self.global_menu_window.set_pointing_to(self.global_menu_button_window)
self.global_menu_button_help = create_menu_button(
"Help",
lambda _: self.global_menu_help.toggle_mousecapture(),
"Help", lambda _: self.global_menu_help.toggle()
)
self.global_menu_help.child_window.set_pointing_to(self.global_menu_button_help)
self.global_menu_help.set_pointing_to(self.global_menu_button_help)
self.all_menu_buttons = [
self.menu_button,
@@ -305,7 +294,7 @@ class GlobalMenuDropdowns:
def _on_title_button_clicked(self, _):
if has_active_window():
self.global_menu_title.toggle_mousecapture()
self.global_menu_title.set_visible(not self.global_menu_title.get_visible())
def _on_active_app_changed(self, _, value):
self.global_title_menu_about.set_property("label", f"About {value}")
@@ -366,11 +355,9 @@ class GlobalMenu(Box):
]
def show_system_dropdown(self, imac_button):
self.dropdown_system.menu_button_dropdown.child_window.set_pointing_to(
imac_button
)
self.dropdown_system.menu_button_dropdown.set_pointing_to(imac_button)
mouse_capture = self.dropdown_system.menu_button_dropdown
mouse_capture.set_child_window_visible(not mouse_capture.is_visible())
mouse_capture.toggle()
def destroy(self):
"""Clean up the global menu and its dropdowns"""
+56 -56
View File
@@ -2,12 +2,11 @@ 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.widgets.wayland import WaylandWindow as Window
from services.battery import BatteryService, DeviceState
from services.battery import Battery
from services.network import NetworkClient
from shared.window.battery_widget import BatteryControl
from shared.window.mousecapture import DropDownMouseCapture
from shared.window.applet_window import AppletWindow
from utils.functions import format_duration, get_wifi_icon_for_strength
from utils.roam import modus_service
from utils.utils import setup_cursor_hover, svg_file
@@ -15,45 +14,41 @@ from window.controlcenter.bluetooth import BluetoothConnections
from window.controlcenter.wifi import WifiConnections
def create_control_window(name_prefix):
return Window(
def create_control_window(name_prefix, parent=None, pointing_to=None):
return AppletWindow(
parent=parent,
pointing_to=pointing_to,
layer="overlay",
title="modus",
anchor="top right",
margin="2px 10px 0px 0px",
exclusivity="auto",
keyboard_mode="on-demand",
name=f"{name_prefix}-window",
visible=False,
)
def create_mouse_capture(window):
return DropDownMouseCapture(layer="top", child_window=window)
def setup_control_center(
show_window, name_prefix, widget_class, parent, **widget_kwargs
show_window, name_prefix, widget_class, parent, pointing_to=None, **widget_kwargs
):
if not show_window:
return None, None, None
return None, None
window = create_control_window(name_prefix)
window = create_control_window(name_prefix, parent=parent, pointing_to=pointing_to)
widget = widget_class(parent, **widget_kwargs)
window.children = [widget]
mouse_capture = create_mouse_capture(window)
return window, widget, mouse_capture
return window, widget
def handle_indicator_click(mouse_capture):
if mouse_capture:
mouse_capture.toggle_mousecapture()
def handle_indicator_click(window):
if window:
window.toggle()
def hide_control_center(mouse_capture):
if mouse_capture:
mouse_capture.hide_child_window()
def hide_control_center(window):
if window:
window.hide()
class BluetoothIndicator(Box):
@@ -77,16 +72,14 @@ class BluetoothIndicator(Box):
(
self.bluetooth_window,
self.bluetooth_widget,
self.bluetooth_mousecapture,
) = setup_control_center(
self.show_window,
"bluetooth",
BluetoothConnections,
self,
pointing_to=self.bt_button,
show_back_button=False,
)
if self.bluetooth_window:
self.bluetooth_window._pointing_widget = self.bt_button
modus_service.connect("bluetooth-changed", self.on_bluetooth_changed)
self.bluetooth.connect("changed", self.on_bluetooth_direct_changed)
@@ -155,13 +148,13 @@ class BluetoothIndicator(Box):
modus_service.bluetooth = bluetooth_state
def on_bluetooth_clicked(self, *args):
handle_indicator_click(self.bluetooth_mousecapture)
handle_indicator_click(self.bluetooth_window)
def close_bluetooth(self, *args):
hide_control_center(self.bluetooth_mousecapture)
hide_control_center(self.bluetooth_window)
def hide_controlcenter(self, *args):
hide_control_center(self.bluetooth_mousecapture)
hide_control_center(self.bluetooth_window)
class NetworkIndicator(Box):
@@ -188,12 +181,12 @@ class NetworkIndicator(Box):
(
self.wifi_window,
self.wifi_widget,
self.wifi_mousecapture,
) = setup_control_center(
self.show_window,
"wifi",
WifiConnections,
self,
pointing_to=self.network_button,
show_back_button=False,
)
if self.wifi_window:
@@ -232,6 +225,7 @@ class NetworkIndicator(Box):
def on_network_direct_changed(self, *args):
self.update_modus_service_wlan_state()
self.update_state()
hide_control_center(self.wifi_window)
def on_network_changed(self, *args):
self.update_modus_service_wlan_state()
@@ -309,13 +303,19 @@ class NetworkIndicator(Box):
self.network_button.set_tooltip_text(tooltip)
def on_wifi_clicked(self, *args):
handle_indicator_click(self.wifi_mousecapture)
handle_indicator_click(self.wifi_window)
def close_wifi(self, *args):
hide_control_center(self.wifi_mousecapture)
hide_control_center(self.wifi_window)
def on_wifi_direct_changed(self, *args):
hide_control_center(self.wifi_window)
def on_network_clicked(self, *args):
handle_indicator_click(self.wifi_window)
def hide_controlcenter(self, *args):
hide_control_center(self.wifi_mousecapture)
hide_control_center(self.wifi_window)
class BatteryIndicator(Box):
@@ -323,7 +323,7 @@ class BatteryIndicator(Box):
super().__init__(name="battery-indicator", orientation="h", **kwargs)
self.show_window = show_window
self.battery_service = BatteryService()
self.battery_service = Battery()
self.battery_icon = svg_file("battery/battery-100.svg", size=23)
@@ -344,12 +344,12 @@ class BatteryIndicator(Box):
(
self.battery_window,
self.battery_widget,
self.battery_mousecapture,
) = setup_control_center(
self.show_window,
"battery",
BatteryControl,
self,
pointing_to=self.battery_button,
show_back_button=False,
)
if self.battery_window:
@@ -367,22 +367,28 @@ class BatteryIndicator(Box):
def on_battery_direct_changed(self, *args):
self.update_modus_service_battery_state()
self.update_state()
hide_control_center(self.battery_window)
def _get_percentage(self):
return int(self.battery_service.get_property("Percentage") or 0)
return int(self.battery_service.percent)
def _get_state(self):
state_value = self.battery_service.get_property("State")
return DeviceState.get(state_value, "UNKNOWN")
if self.battery_service.charging:
return "CHARGING"
if self.battery_service.discharging:
return "DISCHARGING"
if self.battery_service.charged:
return "FULLY_CHARGED"
return "UNKNOWN"
def _is_present(self):
return bool(self.battery_service.get_property("IsPresent"))
return self.battery_service.available
def _get_time_to_empty(self):
return int(self.battery_service.get_property("TimeToEmpty") or 0)
return self.battery_service.time_remaining
def _get_time_to_full(self):
return int(self.battery_service.get_property("TimeToFull") or 0)
return self.battery_service.time_to_full
def _format_time(self, seconds: int) -> str:
return format_duration(seconds)
@@ -433,10 +439,10 @@ class BatteryIndicator(Box):
def update_state(self):
if not self._is_present():
print("[Battery] No battery detected, hiding indicator")
self.set_visible(False)
self.hide()
return
else:
self.set_visible(True)
self.show()
percentage = self._get_percentage()
state = self._get_state()
@@ -451,13 +457,13 @@ class BatteryIndicator(Box):
self.battery_label.set_label(percentage_text)
def on_battery_clicked(self, *args):
handle_indicator_click(self.battery_mousecapture)
handle_indicator_click(self.battery_window)
def close_battery(self, *args):
hide_control_center(self.battery_mousecapture)
hide_control_center(self.battery_window)
def hide_controlcenter(self, *args):
hide_control_center(self.battery_mousecapture)
hide_control_center(self.battery_window)
def destroy(self):
"""Clean up resources when the indicator is destroyed."""
@@ -466,8 +472,8 @@ class BatteryIndicator(Box):
modus_service.disconnect_by_func(self.on_battery_changed)
if hasattr(self, "battery_service") and self.battery_service:
self.battery_service.disconnect_by_func(self.on_battery_direct_changed)
except Exception:
pass
except Exception as e:
print(f"Error disconnecting battery indicator signals: {e}")
# Destroy window and widget
try:
@@ -475,8 +481,6 @@ class BatteryIndicator(Box):
self.battery_widget.destroy()
if hasattr(self, "battery_window") and self.battery_window:
self.battery_window.destroy()
if hasattr(self, "battery_mousecapture") and self.battery_mousecapture:
self.battery_mousecapture.destroy()
except Exception:
pass
@@ -494,15 +498,13 @@ def add_destroy_to_indicators():
self.bluetooth.disconnect_by_func(self.on_bluetooth_direct_changed)
self.bluetooth.disconnect_by_func(self.on_device_added)
self.bluetooth.disconnect_by_func(self.on_device_removed)
except Exception:
pass
except Exception as e:
print(f"Error disconnecting bluetooth indicator signals: {e}")
try:
if hasattr(self, "bluetooth_widget") and self.bluetooth_widget:
self.bluetooth_widget.destroy()
if hasattr(self, "bluetooth_window") and self.bluetooth_window:
self.bluetooth_window.destroy()
if hasattr(self, "bluetooth_mousecapture") and self.bluetooth_mousecapture:
self.bluetooth_mousecapture.destroy()
except Exception:
pass
Box.destroy(self)
@@ -539,15 +541,13 @@ def add_destroy_to_indicators():
)
except Exception:
pass
except Exception:
pass
except Exception as e:
print(f"Error disconnecting network indicator signals: {e}")
try:
if hasattr(self, "wifi_widget") and self.wifi_widget:
self.wifi_widget.destroy()
if hasattr(self, "wifi_window") and self.wifi_window:
self.wifi_window.destroy()
if hasattr(self, "wifi_mousecapture") and self.wifi_mousecapture:
self.wifi_mousecapture.destroy()
except Exception:
pass
Box.destroy(self)
+11 -20
View File
@@ -8,7 +8,7 @@ from fabric.widgets.wayland import WaylandWindow as Window
from services.config import on_config_change, get_config_all
from services.modus import notification_service
from shared.window.mousecapture import MouseCapture
from utils.roam import modus_service
from utils.utils import setup_cursor_hover, svg_file
from window.controlcenter.main import ModusControlCenter
@@ -78,20 +78,18 @@ class Panel(Window):
)
setup_cursor_hover(self.search, "pointer")
self.control_center = MouseCapture(
layer="top", child_window=ModusControlCenter()
)
self.control_center_btn = Button(
name="panel-button",
child=svg_file("misc/control-center.svg", size=22),
on_clicked=self.control_center.toggle_mousecapture,
)
setup_cursor_hover(self.control_center_btn, "pointer")
self.control_center.child_window._pointing_widget = self.control_center_btn
self.notification_center = MouseCapture(
layer="overlay", child_window=NotificationCenter()
self.control_center = ModusControlCenter(
parent=self, pointing_to=self.control_center_btn
)
self.control_center_btn.connect(
"clicked",
lambda *_: self.control_center.toggle(),
)
self.notification_icon = svg_file(
@@ -104,8 +102,9 @@ class Panel(Window):
on_clicked=self.on_notification_icon_clicked,
)
setup_cursor_hover(self.notification_center_btn, "pointer")
self.notification_center.child_window._pointing_widget = (
self.notification_center_btn
self.notification_center = NotificationCenter(
parent=self, pointing_to=self.notification_center_btn
)
self.datetime_btn = Button(
@@ -174,9 +173,7 @@ class Panel(Window):
def on_notification_icon_clicked(self, *args):
count = notification_service.count
if count > 0:
# Only open notification center if there are notifications
self.notification_center.toggle_mousecapture()
# Do nothing if no notifications
self.notification_center.toggle()
def update_notification_icon(self):
count = notification_service.count
@@ -198,8 +195,6 @@ class Panel(Window):
if config_data is None:
config_data = get_config_all()
print(f"[Panel] Rebuilding layout with config: {config_data}")
# Update indicator visibility directly
battery_visible = config_data.get("battery", True)
network_visible = config_data.get("network", True)
@@ -258,10 +253,6 @@ class Panel(Window):
# Force a layout recalculation
self.queue_resize()
print(
f"[Panel] Battery visibility state: {self.battery_indicator.get_visible()}"
)
def _on_config_changed(self, new_config, old_config):
print("[Panel] Config changed, checking keys...")
keys = {
+14 -3
View File
@@ -1,7 +1,7 @@
import json
from fabric.hyprland.widgets import get_hyprland_connection
from fabric.utils import Gdk
from fabric.utils import Gdk, GLib
from fabric.widgets.box import Box
from fabric.widgets.eventbox import EventBox
from fabric.widgets.image import Image
@@ -198,6 +198,10 @@ class ApplicationSwitcher(Window):
name="window-button",
style_classes=["active"] if i == self.current_index else None,
child=button_content,
events=["button-press-event"],
)
event_box.connect(
"button-press-event", lambda w, e, idx=i: self._on_item_clicked(idx)
)
current_row.add(event_box)
@@ -311,11 +315,18 @@ class ApplicationSwitcher(Window):
address = window.get("address")
if address:
try:
command = f"/dispatch focuswindow address:{address}"
self.conn.send_command(command)
GLib.spawn_command_line_async(
f"hyprctl dispatch 'hl.dsp.focus({{ window = \"address:{address}\" }})'"
)
except Exception as e:
print(f"Failed to focus window: {e}")
def _on_item_clicked(self, index):
self.current_index = index
self.update_selection()
self.activate_selected()
self.hide_switcher()
def grab_keyboard(self):
try:
display = Gdk.Display.get_default()
+11 -26
View File
@@ -6,10 +6,9 @@ from fabric.widgets.button import Button
from fabric.widgets.entry import Entry
from fabric.widgets.label import Label
from fabric.widgets.scrolledwindow import ScrolledWindow
from fabric.widgets.wayland import WaylandWindow as Window
from services.todo import get_todo_service
from shared.window.mousecapture import MouseCapture
from shared.window.applet_window import AppletWindow
from utils.utils import svg_file
@@ -224,18 +223,20 @@ class TodoItem(Box):
self.date_label.set_label(date_text)
class TodoListWidget(Window):
"""Main todo list widget window"""
class TodoListWidget(AppletWindow):
"""Main todo list window"""
def __init__(self, **kwargs):
def __init__(self, parent=None, pointing_to=None, **kwargs):
super().__init__(
parent=parent,
pointing_to=pointing_to,
layer="top",
title="modus-todo",
anchor="top right",
margin="2px 10px 0px 0px",
exclusivity="auto",
keyboard_mode="on-demand",
name="todo-list-window",
visible=False, # Back to hidden by default
name="todo-list-menu",
visible=False,
**kwargs,
)
@@ -416,13 +417,8 @@ class TodoListWidget(Window):
def hide_todo_list(self, *_):
"""Hide the todo list"""
if hasattr(self, "_mousecapture_parent"):
self._mousecapture_parent.toggle_mousecapture()
self.set_visible(False)
def _init_mousecapture(self, mousecapture):
"""Initialize mousecapture parent reference"""
self._mousecapture_parent = mousecapture
self.hide()
self.hide()
def destroy(self):
"""Clean up when destroyed"""
@@ -430,14 +426,3 @@ class TodoListWidget(Window):
get_todo_service().remove_callback(self._on_todo_event)
super().destroy()
class TodoListCapture(MouseCapture):
"""MouseCapture wrapper for the todo list"""
def __init__(self, **kwargs):
super().__init__(
layer="top",
child_window=TodoListWidget(),
**kwargs,
)