✨ feat(notification): implement notification center with persistent storage and DND support

This commit is contained in:
sankalp committed 2025-08-02 19:25:33 +05:45
1 parent 2e7207c4ac
commit 30ca6de913
15 files changed
+1260 -472

No files matched your search

+36 -14
View File
@@ -15,10 +15,10 @@ from widgets.mousecapture import DropDownMouseCapture
class AppName:
def __init__(self, path="/usr/share/applications"):
self.files = os.listdir(path) if os.path.exists(path) else []
self.files = os.listdir(path)
self.path = path
def get_app_name(self, wmclass):
def get_app_name(self, wmclass, format_=False):
desktop_file = ""
for f in self.files:
if f.startswith(wmclass + ".desktop"):
@@ -28,17 +28,39 @@ class AppName:
if desktop_file == "":
return wmclass
try:
with open(os.path.join(self.path, desktop_file), "r") as f:
lines = f.readlines()
for line in lines:
if line.startswith("Name="):
desktop_app_name = line.split("=")[1].strip()
break
except:
return wmclass
with open(os.path.join(self.path, desktop_file), "r") as f:
lines = f.readlines()
for line in lines:
if line.startswith("Name="):
desktop_app_name = line.split("=")[1].strip()
break
return desktop_app_name
def get_app_exec(self, wmclass, format_=False):
desktop_file = ""
for f in self.files:
if f.startswith(wmclass + ".desktop"):
desktop_file = f
desktop_app_name = wmclass
if desktop_file == "":
return wmclass
with open(os.path.join(self.path, desktop_file), "r") as f:
lines = f.readlines()
for line in lines:
if line.startswith("Exec="):
desktop_app_name = line.split("=")[1].strip()
break
return desktop_app_name
def get_desktop_file(self, wmclass):
desktop_file = ""
for f in self.files:
if f.startswith(wmclass + ".desktop"):
desktop_file = f
return desktop_file
def format_app_name(self, title, wmclass, update=False):
name = wmclass
if name == "":
@@ -62,8 +84,6 @@ app_name_class = AppName()
def format_window(title, wmclass):
name = app_name_class.format_app_name(title, wmclass, True)
if not name or name == "":
return "Desktop"
return name
@@ -263,10 +283,11 @@ class MenuBarDropdowns:
"label", f"About {value}"
),
)
self.global_menu_button_title = Button(
child=ActiveWindow(
formatter=FormattedString(
"{ format_window('None', 'Hyprland') if win_title == '' and win_class == '' else format_window(win_title, win_class) }",
"{ format_window(win_title, win_class) if win_title or win_class else 'Hyprland' }",
format_window=format_window,
)
),
@@ -274,6 +295,7 @@ class MenuBarDropdowns:
style_classes="button",
on_clicked=lambda _: self.global_menu_title.toggle_mousecapture(),
)
self.global_menu_title.child_window.set_pointing_to(
self.global_menu_button_title
)
+51 -3
View File
@@ -7,6 +7,7 @@ from fabric.widgets.datetime import DateTime
from fabric.widgets.revealer import Revealer
from fabric.widgets.svg import Svg
from modules.controlcenter.main import ModusControlCenter
from modules.notification.notification_center import NotificationCenter
from modules.panel.components.enhanced_system_tray import apply_enhanced_system_tray
from modules.panel.components.indicators import (
BatteryIndicator,
@@ -15,6 +16,7 @@ from modules.panel.components.indicators import (
)
from modules.panel.components.menubar import MenuBar
from modules.panel.components.recording_indicator import RecordingIndicator
from utils.roam import modus_service
from widgets.mousecapture import MouseCapture
from widgets.wayland import WaylandWindow as Window
@@ -39,12 +41,27 @@ class Panel(Window):
self.imac = Button(
name="panel-button",
child=Svg(
size=22,
size=28,
svg_file=get_relative_path("../../config/assets/icons/misc/logo.svg"),
),
on_clicked=lambda *_: self.menubar.show_system_dropdown(self.imac),
)
# DND indicator
self.dnd_icon = Svg(
size=34,
svg_file=get_relative_path(
"../../config/assets/icons/applets/dnd-clear.svg"
),
)
self.dnd_indicator = Button(
name="dnd-indicator",
child=self.dnd_icon,
style="opacity: 0.2;",
visible=True,
)
self.tray = SystemTray(name="system-tray", spacing=4, icon_size=20)
self.tray_revealer = Revealer(
@@ -81,7 +98,7 @@ class Panel(Window):
name="panel-button",
on_clicked=lambda *_: self.search_apps(),
child=Svg(
size=20,
size=22,
svg_file=get_relative_path("../../config/assets/icons/misc/search.svg"),
),
)
@@ -102,6 +119,18 @@ class Panel(Window):
),
)
# Notification Center with MouseCapture
self.notification_center = MouseCapture(
layer="overlay", child_window=NotificationCenter()
)
# Clickable DateTime for notification center
self.datetime_button = Button(
name="datetime-button",
on_clicked=self.notification_center.toggle_mousecapture,
child=DateTime(name="date-time", formatters=["%a %b %d %I:%M"]),
)
self.recording_indicator = RecordingIndicator()
self.children = CenterBox(
@@ -122,16 +151,22 @@ class Panel(Window):
spacing=4,
orientation="h",
children=[
self.dnd_indicator,
self.tray_revealer,
self.chevron_button,
self.indicators,
self.search,
self.control_center_button,
DateTime(name="date-time", formatters=["%a %b %d %I:%M"]),
self.datetime_button,
],
),
)
# Connect to DND state changes
modus_service.connect("dont-disturb-changed", self.on_dnd_changed)
# Set initial DND state
self.update_dnd_indicator(modus_service.dont_disturb)
return self.show_all()
def search_apps(self):
@@ -149,3 +184,16 @@ class Panel(Window):
self.chevron_button.get_child().set_from_file(
get_relative_path("../../config/assets/icons/misc/chevron-right.svg")
)
def on_dnd_changed(self, _, dnd_state):
"""Handle DND state changes from the service."""
self.update_dnd_indicator(dnd_state)
def update_dnd_indicator(self, dnd_enabled):
"""Update the DND indicator opacity based on DND state."""
if dnd_enabled:
# 100% opacity when DND is enabled
self.dnd_indicator.set_style("opacity: 1.0;")
else:
# 20% opacity when DND is disabled
self.dnd_indicator.set_style("opacity: 0.2;")