Initial notification
This commit is contained in:
1 parent
c8c5502297
commit
7d30435f46
7 files changed
+348
-10
No files matched your search
@@ -6,6 +6,7 @@
|
||||
@import url("./styles/launcher.css");
|
||||
@import url("./styles/osd.css");
|
||||
@import url("./styles/about.css");
|
||||
@import url("./styles/notification.css");
|
||||
|
||||
* {
|
||||
all: unset;
|
||||
|
||||
@@ -8,19 +8,20 @@ from modules.dock import Dock
|
||||
from modules.launcher.main import Launcher
|
||||
from modules.osd import OSD
|
||||
from modules.about import About
|
||||
from modules.notification import EnvNoti
|
||||
|
||||
# from modules.corners import Corners
|
||||
from modules.panel.main import Panel
|
||||
from modules.switcher import ApplicationSwitcher
|
||||
|
||||
for log in [
|
||||
"fabric.hyprland.widgets",
|
||||
"fabric.audio.service",
|
||||
"fabric.bluetooth.service",
|
||||
"services.network",
|
||||
"utils.wayland",
|
||||
]:
|
||||
logger.disable(log)
|
||||
# for log in [
|
||||
# "fabric.hyprland.widgets",
|
||||
# "fabric.audio.service",
|
||||
# "fabric.bluetooth.service",
|
||||
# "services.network",
|
||||
# "utils.wayland",
|
||||
# ]:
|
||||
# logger.disable(log)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
@@ -29,12 +30,13 @@ if __name__ == "__main__":
|
||||
# Load configuration
|
||||
from config.data import load_config
|
||||
|
||||
About().toggle(None)
|
||||
# About().toggle(None)
|
||||
config = load_config()
|
||||
|
||||
panel = Panel()
|
||||
# corners = Corners()
|
||||
dock = Dock()
|
||||
envnoti = EnvNoti()
|
||||
switcher = ApplicationSwitcher()
|
||||
launcher = Launcher()
|
||||
panel.launcher = launcher
|
||||
@@ -49,7 +51,7 @@ if __name__ == "__main__":
|
||||
_ = css_file.connect("changed", lambda *_: set_css())
|
||||
|
||||
# Make sure corners is added to the app
|
||||
app = Application(f"{APP_NAME}", panel, dock, switcher, launcher, osd)
|
||||
app = Application(f"{APP_NAME}", panel, dock, switcher, launcher, envnoti, osd)
|
||||
|
||||
def set_css():
|
||||
app.set_stylesheet_from_file(
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
from typing import cast
|
||||
|
||||
from fabric import Application
|
||||
from fabric.widgets.box import Box
|
||||
from fabric.widgets.label import Label
|
||||
from fabric.widgets.image import Image
|
||||
from fabric.widgets.button import Button
|
||||
from utils.wayland import WaylandWindow as Window
|
||||
from fabric.notifications import (
|
||||
NotificationAction,
|
||||
Notifications,
|
||||
Notification,
|
||||
NotificationImagePixmap,
|
||||
)
|
||||
from fabric.utils import invoke_repeater, get_relative_path
|
||||
|
||||
from utils.roam import modus_service
|
||||
|
||||
from utils.custom_image import CustomImage
|
||||
|
||||
from gi.repository import GdkPixbuf # type: ignore
|
||||
|
||||
|
||||
NOTIFICATION_WIDTH = 360
|
||||
NOTIFICATION_IMAGE_SIZE = 48
|
||||
NOTIFICATION_TIMEOUT = 10 * 1000
|
||||
|
||||
|
||||
class NotificationWidget(Box):
|
||||
def __init__(self, notification: Notification, **kwargs):
|
||||
super().__init__(
|
||||
size=(NOTIFICATION_WIDTH, -1),
|
||||
name="notification",
|
||||
spacing=8,
|
||||
orientation="v",
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
self._notification = notification
|
||||
|
||||
self.image = notification.image_pixbuf
|
||||
body_container = Box(spacing=4, orientation="h")
|
||||
|
||||
if self._notification.image_pixbuf:
|
||||
body_container.add(
|
||||
CustomImage(
|
||||
name="noti-image",
|
||||
pixbuf=self.image.scale_simple( # type: ignore
|
||||
NOTIFICATION_IMAGE_SIZE,
|
||||
NOTIFICATION_IMAGE_SIZE,
|
||||
GdkPixbuf.InterpType.BILINEAR,
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
body_container.add(
|
||||
Box(
|
||||
spacing=4,
|
||||
orientation="v",
|
||||
children=[
|
||||
Box(
|
||||
orientation="h",
|
||||
children=[
|
||||
Label(
|
||||
markup=self._notification.summary, # type: ignore
|
||||
ellipsization="middle",
|
||||
style_classes="summary",
|
||||
)
|
||||
],
|
||||
h_expand=True,
|
||||
v_expand=True,
|
||||
),
|
||||
Label(
|
||||
label=self._notification.body, # type: ignore
|
||||
line_wrap="word-char",
|
||||
v_align="start",
|
||||
h_align="start",
|
||||
style_classes="body",
|
||||
),
|
||||
],
|
||||
h_expand=True,
|
||||
v_expand=True,
|
||||
)
|
||||
)
|
||||
|
||||
self.add(body_container)
|
||||
|
||||
if actions := self._notification.actions:
|
||||
actions = cast(list[NotificationAction], actions) # type: ignore
|
||||
self.add(
|
||||
Box(
|
||||
spacing=4,
|
||||
orientation="h",
|
||||
children=[
|
||||
Button(
|
||||
label=action.label,
|
||||
h_expand=True,
|
||||
name="notification-action",
|
||||
v_expand=True,
|
||||
on_clicked=lambda *_, action=action: (
|
||||
action.invoke(),
|
||||
action.parent.close("dismissed-by-user"),
|
||||
),
|
||||
)
|
||||
for action in actions # type: ignore
|
||||
],
|
||||
)
|
||||
)
|
||||
|
||||
self._notification.connect(
|
||||
"closed",
|
||||
self.destroy_noti,
|
||||
)
|
||||
|
||||
invoke_repeater(
|
||||
NOTIFICATION_TIMEOUT,
|
||||
lambda: self._notification.close("expired"),
|
||||
initial_call=False,
|
||||
)
|
||||
|
||||
def close_noti(self, *_):
|
||||
modus_service.remove_notification(self._notification["id"])
|
||||
self._notification.close()
|
||||
self.destroy()
|
||||
|
||||
def destroy_noti(self, *_):
|
||||
parent.remove(self) if (parent := self.get_parent()) else None, # type: ignore
|
||||
self.destroy()
|
||||
|
||||
|
||||
class EnvNoti(Window):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(
|
||||
margin="8px 8px 8px 8px",
|
||||
name="notification-window",
|
||||
title="Notification Center",
|
||||
layer="overlay",
|
||||
anchor="top right",
|
||||
child=Box(
|
||||
size=2,
|
||||
spacing=4,
|
||||
orientation="v",
|
||||
).build(
|
||||
lambda viewport, _: Notifications(
|
||||
on_notification_added=self.on_notification_added
|
||||
)
|
||||
),
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
def on_notification_added(self, notifs_service, nid):
|
||||
modus_service.cache_notification(notifs_service.get_notification_from_id(nid))
|
||||
if modus_service.dont_disturb == "True":
|
||||
return
|
||||
self.get_child().add(
|
||||
NotificationWidget(
|
||||
cast(Notification, notifs_service.get_notification_from_id(nid))
|
||||
)
|
||||
)
|
||||
@@ -13,6 +13,8 @@ from modules.panel.components.enhanced_system_tray import apply_enhanced_system_
|
||||
from modules.panel.components.indicators import Indicators
|
||||
from modules.panel.components.menubar import MenuBar
|
||||
from modules.panel.components.recording_indicator import RecordingIndicator
|
||||
from utils.roam import modus_service
|
||||
from modules.notification import EnvNoti
|
||||
|
||||
# Apply enhanced system tray icon handling
|
||||
apply_enhanced_system_tray()
|
||||
@@ -49,6 +51,8 @@ class Panel(Window):
|
||||
|
||||
self.tray = SystemTray(name="system-tray", spacing=4, icon_size=20)
|
||||
|
||||
# modus_service.connect("show-notificationcenter-changed", envnoticenter.toggle_mousecapture)
|
||||
|
||||
self.tray_revealer = Revealer(
|
||||
name="tray-revealer",
|
||||
child=self.tray,
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
# type: ignore
|
||||
from fabric.core.service import Service, Signal, Property
|
||||
from fabric.notifications import Notification
|
||||
from typing import List
|
||||
|
||||
|
||||
class ModusService(Service):
|
||||
@Signal
|
||||
def notification_count_changed(self, value: int) -> None: ...
|
||||
@Signal
|
||||
def dont_disturb_changed(self, value: bool) -> None: ...
|
||||
@Signal
|
||||
def show_notificationcenter_changed(self, value: bool) -> None: ...
|
||||
@Property(bool, flags="read-write", default_value=False)
|
||||
def show_notificationcenter(self) -> bool:
|
||||
return self._show_notificationcenter
|
||||
|
||||
@Property(str, flags="read-write")
|
||||
def notification_count(self) -> int:
|
||||
return self._notification_count
|
||||
|
||||
@Property(str, flags="read-write")
|
||||
def dont_disturb(self) -> bool:
|
||||
return self._dont_disturb
|
||||
|
||||
@dont_disturb.setter
|
||||
def dont_disturb(self, value: bool):
|
||||
if value != self._dont_disturb:
|
||||
self._dont_disturb = value
|
||||
self.dont_disturb_changed(value)
|
||||
|
||||
@show_notificationcenter.setter
|
||||
def show_notificationcenter(self, value: bool):
|
||||
if value != self._show_notificationcenter:
|
||||
self._show_notificationcenter = value
|
||||
self.show_notificationcenter_changed(value)
|
||||
|
||||
def sc(self, signal_name: str, callback: callable, def_value="..."):
|
||||
self.connect(signal_name, callback)
|
||||
return def_value
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._notifications = []
|
||||
self._notification_count = len(self._notifications)
|
||||
self._dont_disturb = False
|
||||
self._show_notificationcenter = False
|
||||
|
||||
self.notifications = []
|
||||
|
||||
def remove_notification(self, id: int):
|
||||
item = next((p for p in self._notifications if p["id"] == id), None)
|
||||
if item is None:
|
||||
return
|
||||
index = self._notifications.index(item)
|
||||
|
||||
self._notifications.pop(index)
|
||||
self._notification_count -= 1
|
||||
self.notification_count_changed(self._notification_count)
|
||||
if self._notification_count == 0:
|
||||
self.clear_all_changed(True)
|
||||
|
||||
def cache_notification(self, data: Notification):
|
||||
existing_data = self._notifications
|
||||
serialized_data = data.serialize()
|
||||
serialized_data.update({"id": self._notification_count + 1})
|
||||
existing_data.append(serialized_data)
|
||||
|
||||
self._notification_count += 1
|
||||
self._notifications = existing_data
|
||||
self.notification_count_changed(self._notification_count)
|
||||
|
||||
def clear_all_notifications(self):
|
||||
self._notifications = []
|
||||
self._notification_count = 0
|
||||
|
||||
self.clear_all_changed(True)
|
||||
self.notification_count_changed(self._notification_count)
|
||||
|
||||
def get_deserialized(self) -> List[Notification]:
|
||||
if len(self.notifications) <= 0:
|
||||
self.notifications = [
|
||||
Notification.deserialize(data) for data in self._notifications
|
||||
]
|
||||
return self.notifications
|
||||
@@ -0,0 +1,57 @@
|
||||
|
||||
#notification {
|
||||
padding: 0.8rem;
|
||||
background-color: alpha(#000, 0.3);
|
||||
border: .5px solid alpha(#111, 0.4);
|
||||
box-shadow: inset 0 0 200px 0 alpha(#111, 0.5), inset 0 0 0 .5px alpha(#aaa, 0.4);
|
||||
border-radius: 1.2rem;
|
||||
}
|
||||
|
||||
#notification .summary {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#notification .body {
|
||||
color: #ddd;
|
||||
margin-top: 0.5rem;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
#noti-image {
|
||||
border-radius: 7px;
|
||||
}
|
||||
|
||||
#env-noti-center {
|
||||
border-radius: 1.25rem;
|
||||
}
|
||||
|
||||
#notification-action{
|
||||
background-color: #e0e0e0;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
padding: 6px 12px;
|
||||
font-weight: 500;
|
||||
font-size: 0.9rem;
|
||||
transition: background-color 0.2s ease, color 0.2s ease;
|
||||
}
|
||||
#notification-action label {
|
||||
color: #000000;
|
||||
}
|
||||
#notification-action:hover label {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
#notification-action:hover {
|
||||
background-color: #007aff; /* macOS-style blue */
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
#notification-center {
|
||||
background-color: alpha(#000, 0.3);
|
||||
box-shadow: inset 0 0 0 1px alpha(#aaa, 0.4);
|
||||
border: 1px solid alpha(#111, 0.4);
|
||||
border-radius: 1.25rem;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import sys
|
||||
import os
|
||||
|
||||
# from hyprpy import Hyprland
|
||||
from fabric.audio import Audio
|
||||
from fabric.notifications import Notifications
|
||||
from services.modus import ModusService
|
||||
from loguru import logger
|
||||
|
||||
|
||||
global modus_service
|
||||
try:
|
||||
modus_service = ModusService()
|
||||
except Exception as e:
|
||||
logger.error("[Main] Failed to create ModusService:", e)
|
||||
sys.exit(1)
|
||||
|
||||
# global notification_service
|
||||
# try:
|
||||
# notification_service = Notifications()
|
||||
# except Exception as e:
|
||||
# logger.error("[Main] Failed to create NotificationService:", e)
|
||||
# sys.exit(1)
|
||||
|
||||
global audio_service
|
||||
try:
|
||||
audio_service = Audio()
|
||||
except Exception as e:
|
||||
logger.error("[Main] Failed to create AudioService:", e)
|
||||
sys.exit(1)
|
||||
Reference in new issue
Block a user