diff --git a/src/window/controlcenter/bluetooth.py b/src/window/controlcenter/bluetooth.py index 031eed4..0a47196 100644 --- a/src/window/controlcenter/bluetooth.py +++ b/src/window/controlcenter/bluetooth.py @@ -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 ), ) diff --git a/src/window/controlcenter/expanded_player.py b/src/window/controlcenter/expanded_player.py index 73ffe0c..66f6ee3 100644 --- a/src/window/controlcenter/expanded_player.py +++ b/src/window/controlcenter/expanded_player.py @@ -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) diff --git a/src/window/controlcenter/main.py b/src/window/controlcenter/main.py index 4012b5a..713009e 100644 --- a/src/window/controlcenter/main.py +++ b/src/window/controlcenter/main.py @@ -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: diff --git a/src/window/controlcenter/per_app_volume.py b/src/window/controlcenter/per_app_volume.py index af63e82..5ce1a1f 100644 --- a/src/window/controlcenter/per_app_volume.py +++ b/src/window/controlcenter/per_app_volume.py @@ -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 diff --git a/src/window/controlcenter/wifi.py b/src/window/controlcenter/wifi.py index 929a8f3..181697f 100644 --- a/src/window/controlcenter/wifi.py +++ b/src/window/controlcenter/wifi.py @@ -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( diff --git a/src/window/notification/notification_center.py b/src/window/notification/notification_center.py index e519389..b900b8f 100644 --- a/src/window/notification/notification_center.py +++ b/src/window/notification/notification_center.py @@ -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: diff --git a/src/window/panel/components/globalmenu.py b/src/window/panel/components/globalmenu.py index 8b5a3f4..c0aa7ab 100644 --- a/src/window/panel/components/globalmenu.py +++ b/src/window/panel/components/globalmenu.py @@ -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""" diff --git a/src/window/panel/components/indicators.py b/src/window/panel/components/indicators.py index 34a8f2a..014e4ea 100644 --- a/src/window/panel/components/indicators.py +++ b/src/window/panel/components/indicators.py @@ -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) diff --git a/src/window/panel/main.py b/src/window/panel/main.py index 04e4d94..8c9d3f1 100644 --- a/src/window/panel/main.py +++ b/src/window/panel/main.py @@ -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 = { diff --git a/src/window/switcher.py b/src/window/switcher.py index f8a86e2..94f62ca 100644 --- a/src/window/switcher.py +++ b/src/window/switcher.py @@ -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() diff --git a/src/window/todo/todo_widget.py b/src/window/todo/todo_widget.py index a487ae5..5fb2dc9 100644 --- a/src/window/todo/todo_widget.py +++ b/src/window/todo/todo_widget.py @@ -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, - )