diff --git a/main.py b/main.py index f7cc6d1..95bebf4 100644 --- a/main.py +++ b/main.py @@ -12,7 +12,7 @@ from modules.launcher import Launcher from modules.notification_popup import NotificationPopup from modules.osd import OSD from modules.switcher import ApplicationSwitcher -from modules.dashboard import DashboardWindow +from modules.dashboard.main import DashboardWindow for log in [ diff --git a/modules/dashboard/__init__.py b/modules/dashboard/__init__.py deleted file mode 100644 index 9d69267..0000000 --- a/modules/dashboard/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -""" -Dashboard module for Modus. -Provides a dashboard interface with tiles and notifications. -""" - -from .main import DashboardWindow -from .notifications import DashboardNotifications, DashboardNotificationItem - -__all__ = ["DashboardWindow", "DashboardNotifications", "DashboardNotificationItem"] diff --git a/modules/dashboard/bluetooth.py b/modules/dashboard/bluetooth.py index a5d3c69..ba8a10a 100644 --- a/modules/dashboard/bluetooth.py +++ b/modules/dashboard/bluetooth.py @@ -6,8 +6,8 @@ from fabric.widgets.image import Image from fabric.widgets.label import Label from fabric.widgets.scrolledwindow import ScrolledWindow -from modules.dashboard.tile import Tile, add_hover_cursor import utils.icons as icons +from modules.dashboard.tile import Tile, add_hover_cursor class BluetoothDeviceSlot(CenterBox): @@ -22,8 +22,7 @@ class BluetoothDeviceSlot(CenterBox): ) self.connection_label = Label( - name="bluetooth-connection", - markup=icons.bluetooth_disconnected + name="bluetooth-connection", markup=icons.bluetooth_disconnected ) self.connect_button = Button( name="bluetooth-connect", @@ -96,10 +95,16 @@ class Bluetooth(Tile): # Initialize bluetooth client with device handling self.bluetooth_client = BluetoothClient(on_device_added=self.on_device_added) - self.bluetooth_client.connect("notify::enabled", self._on_bluetooth_state_changed) + self.bluetooth_client.connect( + "notify::enabled", self._on_bluetooth_state_changed + ) self.bluetooth_client.connect("notify::scanning", self._update_scan_label) - self.bluetooth_client.connect("device-added", self._on_device_connection_changed) - self.bluetooth_client.connect("device-removed", self._on_device_connection_changed) + self.bluetooth_client.connect( + "device-added", self._on_device_connection_changed + ) + self.bluetooth_client.connect( + "device-removed", self._on_device_connection_changed + ) # Initialize state self._update_tile_state() @@ -124,7 +129,7 @@ class Bluetooth(Tile): def _setup_content_button(self): """Setup the content button to toggle Bluetooth instead of showing menu.""" - if hasattr(self, 'type_box'): + if hasattr(self, "type_box"): # Disconnect the existing click handler self.type_box.disconnect_by_func(self.handle_click) # Connect to Bluetooth toggle handler @@ -160,14 +165,13 @@ class Bluetooth(Tile): """Create the detailed bluetooth content for the dashboard.""" # Create back button with chevron left icon self.back_button_icon = Label( - name="bluetooth-back-label", - markup=icons.chevron_left + name="bluetooth-back-label", markup=icons.chevron_left ) self.back_button = Button( name="bluetooth-back", child=self.back_button_icon, tooltip_text="Back to notifications", - on_clicked=self._on_back_clicked + on_clicked=self._on_back_clicked, ) add_hover_cursor(self.back_button) @@ -196,7 +200,7 @@ class Bluetooth(Tile): name="bluetooth-header", start_children=[self.back_button], center_children=[Label(name="bluetooth-title", label="Bluetooth Devices")], - end_children=[self.scan_button] + end_children=[self.scan_button], ) # Main container @@ -218,7 +222,7 @@ class Bluetooth(Tile): propagate_width=False, propagate_height=False, ), - ] + ], ) # Initialize notifications @@ -267,5 +271,3 @@ class Bluetooth(Tile): def _on_device_connection_changed(self, *_): """Handle device connection/disconnection changes.""" self._update_tile_icon() - - diff --git a/modules/dashboard/main.py b/modules/dashboard/main.py index bce838e..aa77434 100644 --- a/modules/dashboard/main.py +++ b/modules/dashboard/main.py @@ -1,13 +1,12 @@ +from fabric.core.service import Property from fabric.widgets.box import Box from fabric.widgets.stack import Stack -from fabric.core.service import Property from modules.dashboard.bluetooth import Bluetooth from modules.dashboard.network import Network -from modules.dashboard.tile import TileSpecial -from modules.dashboard.player_mini import PlayerContainerMini from modules.dashboard.notifications import DashboardNotifications - +from modules.dashboard.player_mini import PlayerContainerMini +from modules.dashboard.tile import TileSpecial from utils.wayland import WaylandWindow as Window diff --git a/modules/dashboard/network.py b/modules/dashboard/network.py index dea5967..46b4c9d 100644 --- a/modules/dashboard/network.py +++ b/modules/dashboard/network.py @@ -1,21 +1,22 @@ -from fabric.utils import bulk_connect from fabric.widgets.box import Box from fabric.widgets.button import Button from fabric.widgets.centerbox import CenterBox from fabric.widgets.image import Image from fabric.widgets.label import Label from fabric.widgets.scrolledwindow import ScrolledWindow -from gi.repository import Gtk, GLib +from gi.repository import GLib +import utils.icons as icons from modules.dashboard.tile import Tile, add_hover_cursor from services.network import NetworkClient -import utils.icons as icons class WifiAccessPointSlot(CenterBox): """A widget representing a single WiFi access point in the dashboard.""" - def __init__(self, access_point, network_service: NetworkClient, wifi_service, **kwargs): + def __init__( + self, access_point, network_service: NetworkClient, wifi_service, **kwargs + ): super().__init__(name="wifi-ap-slot", **kwargs) self.access_point = access_point self.network_service = network_service @@ -28,10 +29,7 @@ class WifiAccessPointSlot(CenterBox): self.ap_icon = Image(icon_name=icon_name, size=24) self.ap_label = Label( - label=ssid, - h_expand=True, - h_align="start", - ellipsization="end" + label=ssid, h_expand=True, h_align="start", ellipsization="end" ) self.connect_button = Button( @@ -43,12 +41,19 @@ class WifiAccessPointSlot(CenterBox): ) add_hover_cursor(self.connect_button) - self.set_start_children([ - Box(spacing=8, h_expand=True, h_align="fill", children=[ - self.ap_icon, - self.ap_label, - ]) - ]) + self.set_start_children( + [ + Box( + spacing=8, + h_expand=True, + h_align="fill", + children=[ + self.ap_icon, + self.ap_label, + ], + ) + ] + ) self.set_end_children([self.connect_button]) def _on_connect_clicked(self, *_): @@ -115,7 +120,14 @@ class Network(Tile): def _animate_searching(self): """Animate wifi icon when searching for networks""" - wifi_icons = [icons.wifi_0, icons.wifi_1, icons.wifi_2, icons.wifi_3, icons.wifi_2, icons.wifi_1] + wifi_icons = [ + icons.wifi_0, + icons.wifi_1, + icons.wifi_2, + icons.wifi_3, + icons.wifi_2, + icons.wifi_1, + ] wifi = self.network_client.wifi_device if not self.icon or not wifi or not wifi.wireless_enabled: @@ -218,7 +230,7 @@ class Network(Tile): def _setup_content_button(self): """Setup the content button to toggle WiFi instead of showing menu.""" - if hasattr(self, 'type_box'): + if hasattr(self, "type_box"): # Disconnect the existing click handler self.type_box.disconnect_by_func(self.handle_click) # Connect to WiFi toggle handler @@ -235,35 +247,30 @@ class Network(Tile): """Create the detailed network content for the dashboard.""" # Create status label self.status_label = Label( - label="Initializing ", - h_expand=True, - visible=False, - h_align="center" + label="Initializing ", h_expand=True, visible=False, h_align="center" ) # Create refresh button with icon self.refresh_button_icon = Label( - name="network-refresh-label", - markup=icons.reload + name="network-refresh-label", markup=icons.reload ) self.refresh_button = Button( name="network-refresh", child=self.refresh_button_icon, tooltip_text="Scan for Wi-Fi networks", - on_clicked=self._refresh_access_points + on_clicked=self._refresh_access_points, ) add_hover_cursor(self.refresh_button) # Create back button with chevron left icon self.back_button_icon = Label( - name="network-back-label", - markup=icons.chevron_left + name="network-back-label", markup=icons.chevron_left ) self.back_button = Button( name="network-back", child=self.back_button_icon, tooltip_text="Back to notifications", - on_clicked=self._on_back_clicked + on_clicked=self._on_back_clicked, ) add_hover_cursor(self.back_button) @@ -272,7 +279,7 @@ class Network(Tile): name="network-header", start_children=[self.back_button], center_children=[Label(name="network-title", label="Wi-Fi Networks")], - end_children=[self.refresh_button] + end_children=[self.refresh_button], ) # Create access points list @@ -299,7 +306,7 @@ class Network(Tile): header_box, self.status_label, scrolled_window, - ] + ], ) # Initialize button states @@ -322,26 +329,30 @@ class Network(Tile): """Setup WiFi device connections and initial state.""" if self.network_client.wifi_device: self.network_client.wifi_device.connect("changed", self._load_access_points) - self.network_client.wifi_device.connect("notify::wireless-enabled", self._update_wifi_status_ui) - self.network_client.wifi_device.connect("notify::active-access-point", self._update_tile_state) + self.network_client.wifi_device.connect( + "notify::wireless-enabled", self._update_wifi_status_ui + ) + self.network_client.wifi_device.connect( + "notify::active-access-point", self._update_tile_state + ) # Only update UI if content has been created - if self._content_created and hasattr(self, 'refresh_button'): + if self._content_created and hasattr(self, "refresh_button"): self._update_wifi_status_ui() # Update tile icon immediately self._update_tile_icon() else: # Only show error if content has been created - if self._content_created and hasattr(self, 'status_label'): + if self._content_created and hasattr(self, "status_label"): self.status_label.set_label("Wi-Fi device not available.") self.status_label.set_visible(True) - if hasattr(self, 'refresh_button'): + if hasattr(self, "refresh_button"): self.refresh_button.set_sensitive(False) def _update_wifi_status_ui(self, *_): """Update WiFi status UI elements.""" - if not hasattr(self, 'refresh_button') or not self._content_created: + if not hasattr(self, "refresh_button") or not self._content_created: return # Content not created yet if self.network_client.wifi_device: @@ -362,7 +373,10 @@ class Network(Tile): def _refresh_access_points(self, *_): """Refresh access points list.""" - if self.network_client.wifi_device and self.network_client.wifi_device.wireless_enabled: + if ( + self.network_client.wifi_device + and self.network_client.wifi_device.wireless_enabled + ): self.status_label.set_label("Scanning for Wi-Fi networks...") self.status_label.set_visible(True) self._clear_ap_list() @@ -376,10 +390,13 @@ class Network(Tile): def _load_access_points(self, *_): """Load and display access points.""" - if not hasattr(self, 'ap_list_box'): + if not hasattr(self, "ap_list_box"): return # Content not created yet - if not self.network_client.wifi_device or not self.network_client.wifi_device.wireless_enabled: + if ( + not self.network_client.wifi_device + or not self.network_client.wifi_device.wireless_enabled + ): self._clear_ap_list() self.status_label.set_label("Wi-Fi disabled.") self.status_label.set_visible(True) @@ -395,12 +412,12 @@ class Network(Tile): else: self.status_label.set_visible(False) # Sort access points by connection status (connected first) then by signal strength - sorted_aps = sorted(access_points, key=lambda x: (not x.is_active, -x.strength)) + sorted_aps = sorted( + access_points, key=lambda x: (not x.is_active, -x.strength) + ) for access_point in sorted_aps: slot = WifiAccessPointSlot( access_point, self.network_client, self.network_client.wifi_device ) self.ap_list_box.add(slot) self.ap_list_box.show_all() - - diff --git a/modules/dashboard/notifications.py b/modules/dashboard/notifications.py index ceba117..ed17e16 100644 --- a/modules/dashboard/notifications.py +++ b/modules/dashboard/notifications.py @@ -3,49 +3,49 @@ Dashboard notification components for Modus. Provides notification display functionality for the dashboard. """ -from fabric.widgets.box import Box -from fabric.widgets.label import Label -from fabric.widgets.button import Button -from fabric.widgets.centerbox import CenterBox -from fabric.widgets.scrolledwindow import ScrolledWindow -from gi.repository import GLib, Gtk -import os import json +import os from datetime import datetime -from utils.notification_utils import ( - get_shared_notification_history, - PERSISTENT_HISTORY_FILE, - create_historical_notification_from_data, - compute_time_label, -) +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.scrolledwindow import ScrolledWindow +from gi.repository import GLib, Gtk import utils.icons as icons +from utils.notification_utils import ( + PERSISTENT_HISTORY_FILE, + compute_time_label, + create_historical_notification_from_data, + get_shared_notification_history, +) class DashboardNotificationItem(Box): """Individual notification item for dashboard""" - + def __init__(self, notification_data, **kwargs): super().__init__( name="notification-container", orientation="v", h_align="fill", h_expand=True, - **kwargs + **kwargs, ) - + self.notification_data = notification_data - + # Create historical notification from data hist_notif = create_historical_notification_from_data(notification_data) - + # Create timestamp try: arrival_time = datetime.fromisoformat(hist_notif.timestamp) except Exception: arrival_time = datetime.now() - + # Create time label self.time_label = Label( name="notification-timestamp", @@ -53,7 +53,7 @@ class DashboardNotificationItem(Box): h_align="start", ellipsization="end", ) - + # Create notification image box # For dashboard, we'll use a simple icon instead of trying to load cached images self.notif_image_box = Box( @@ -64,7 +64,7 @@ class DashboardNotificationItem(Box): Box(v_expand=True), ], ) - + # Create summary label self.notif_summary_label = Label( name="notification-summary", @@ -72,7 +72,7 @@ class DashboardNotificationItem(Box): h_align="start", ellipsization="end", ) - + # Create app name label self.notif_app_name_label = Label( name="notification-app-name", @@ -80,7 +80,7 @@ class DashboardNotificationItem(Box): h_align="start", ellipsization="end", ) - + # Create body label self.notif_body_label = ( Label( @@ -95,7 +95,7 @@ class DashboardNotificationItem(Box): ) if hist_notif.body: self.notif_body_label.set_single_line_mode(True) - + # Create summary box with separators self.notif_summary_box = Box( name="notification-summary-box", @@ -120,7 +120,7 @@ class DashboardNotificationItem(Box): self.time_label, ], ) - + # Create text box self.notif_text_box = Box( name="notification-text", @@ -132,14 +132,14 @@ class DashboardNotificationItem(Box): self.notif_body_label, ], ) - + # Create close button self.notif_close_button = Button( name="notif-close-button", child=Label(name="notif-close-label", markup=icons.cancel), on_clicked=lambda *_: self.remove_notification(), ) - + self.notif_close_button_box = Box( orientation="v", children=[ @@ -147,7 +147,7 @@ class DashboardNotificationItem(Box): Box(v_expand=True), ], ) - + # Create main content box content_box = Box( name="notification-box-hist", @@ -158,9 +158,9 @@ class DashboardNotificationItem(Box): self.notif_close_button_box, ], ) - + self.add(content_box) - + def remove_notification(self): """Remove this notification from the dashboard""" if self.get_parent(): @@ -170,17 +170,13 @@ class DashboardNotificationItem(Box): class DashboardNotifications(Box): """Dashboard notifications container that shows real notifications""" - + def __init__(self, **kwargs): - super().__init__( - name="notification-history", - orientation="v", - **kwargs - ) - + super().__init__(name="notification-history", orientation="v", **kwargs) + self.notification_items = [] self.max_notifications = 5 # Limit to 5 notifications in dashboard - + # Create header components (matching dock notifications) self.header_label = Label( name="nhh", @@ -208,7 +204,7 @@ class DashboardNotifications(Box): center_children=[self.header_label], end_children=[self.header_clean], ) - + # Create notifications list self.notifications_list = Box( name="notifications-list", @@ -219,7 +215,7 @@ class DashboardNotifications(Box): h_align="fill", v_align="fill", ) - + # Create "no notifications" label self.no_notifications_label = Label( name="no-notif", @@ -238,7 +234,7 @@ class DashboardNotifications(Box): h_expand=True, children=[self.no_notifications_label], ) - + # Create scrolled window for notifications list only self.notifications_scrolled = ScrolledWindow( name="notification-history-scrolled-window", @@ -261,51 +257,55 @@ class DashboardNotifications(Box): # Add the no notifications box initially to the notifications list self.notifications_list.add(self.no_notifications_box) - + # Connect to shared notification history self._connect_to_notifications() - + # Load and sync DND state self._load_and_sync_dnd_state() - + # Load initial notifications self._load_recent_notifications() - + def _connect_to_notifications(self): """Connect to the shared notification history for real-time updates""" try: self.shared_history = get_shared_notification_history() - self.shared_history.connect("notification-added", self._on_notification_added) + self.shared_history.connect( + "notification-added", self._on_notification_added + ) self.shared_history.connect("dnd-state-changed", self._on_dnd_state_changed) except Exception as e: print(f"Could not connect to notification history: {e}") - + def _on_notification_added(self, shared_history): """Handle new notification added""" GLib.idle_add(self._refresh_notifications) - + def _on_dnd_state_changed(self, notification_history, dnd_enabled): """Handle DND state change""" self.do_not_disturb_enabled = dnd_enabled self.header_switch.set_active(dnd_enabled) - + def _load_and_sync_dnd_state(self): """Load and sync DND state from shared history""" try: - if hasattr(self, 'shared_history'): + if hasattr(self, "shared_history"): dnd_enabled = self.shared_history.do_not_disturb_enabled self.header_switch.set_active(dnd_enabled) self.do_not_disturb_enabled = dnd_enabled except Exception as e: print(f"Could not sync DND state: {e}") - + def on_do_not_disturb_changed(self, switch, pspec): """Handle DND switch toggle""" self.do_not_disturb_enabled = switch.get_active() - + try: - if hasattr(self, 'shared_history'): - self.shared_history.set_do_not_disturb_enabled(self.do_not_disturb_enabled) + if hasattr(self, "shared_history"): + self.shared_history.set_do_not_disturb_enabled( + self.do_not_disturb_enabled + ) except Exception as e: print(f"Could not update shared DND state: {e}") @@ -319,7 +319,7 @@ class DashboardNotifications(Box): # Clear persistent history try: - if hasattr(self, 'shared_history'): + if hasattr(self, "shared_history"): self.shared_history.clear_history() except Exception as e: print(f"Could not clear shared history: {e}") @@ -335,7 +335,7 @@ class DashboardNotifications(Box): notifications = json.load(f) # Get the most recent notifications (up to max_notifications) - recent_notifications = notifications[-self.max_notifications:] + recent_notifications = notifications[-self.max_notifications :] for notif_data in reversed(recent_notifications): self._add_notification_item(notif_data) diff --git a/modules/dashboard/player_mini.py b/modules/dashboard/player_mini.py index 809bc12..f842b44 100644 --- a/modules/dashboard/player_mini.py +++ b/modules/dashboard/player_mini.py @@ -1,20 +1,16 @@ -from gi.repository import Gdk +import gi from fabric.widgets.box import Box -from fabric.widgets.label import Label -from fabric.widgets.centerbox import CenterBox from fabric.widgets.button import Button -from fabric.widgets.stack import Stack +from fabric.widgets.centerbox import CenterBox from fabric.widgets.eventbox import EventBox - -from services.mpris import PlayerManager, PlayerService -from utils.wiggle_bar import WigglyWidget - -import utils.icons as icons -import config.data as data - +from fabric.widgets.label import Label +from fabric.widgets.stack import Stack from loguru import logger -import gi +import config.data as data +import utils.icons as icons +from services.mpris import PlayerManager, PlayerService +from utils.wiggle_bar import WigglyWidget gi.require_version("Gtk", "3.0") diff --git a/modules/dashboard/tile.py b/modules/dashboard/tile.py index f6378d0..f04e2f1 100644 --- a/modules/dashboard/tile.py +++ b/modules/dashboard/tile.py @@ -23,9 +23,10 @@ Usage: from fabric.widgets.box import Box from fabric.widgets.button import Button from fabric.widgets.label import Label -from fabric.widgets.stack import Stack from fabric.widgets.revealer import Revealer +from fabric.widgets.stack import Stack from gi.repository import Gdk + import utils.icons as icons