163 lines
5.2 KiB
Python
163 lines
5.2 KiB
Python
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 fabric.widgets.wayland import WaylandWindow
|
|
from fabric.notifications import Notifications, Notification
|
|
from fabric.utils import invoke_repeater, get_relative_path
|
|
|
|
from gi.repository import GdkPixbuf
|
|
|
|
|
|
NOTIFICATION_WIDTH = 360
|
|
NOTIFICATION_IMAGE_SIZE = 64
|
|
NOTIFICATION_TIMEOUT = 10 * 1000 # 10 seconds
|
|
|
|
|
|
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
|
|
|
|
body_container = Box(spacing=4, orientation="h")
|
|
|
|
if image_pixbuf := self._notification.image_pixbuf:
|
|
body_container.add(
|
|
Image(
|
|
pixbuf=image_pixbuf.scale_simple(
|
|
NOTIFICATION_IMAGE_SIZE,
|
|
NOTIFICATION_IMAGE_SIZE,
|
|
GdkPixbuf.InterpType.BILINEAR,
|
|
)
|
|
)
|
|
)
|
|
|
|
body_container.add(
|
|
Box(
|
|
spacing=4,
|
|
orientation="v",
|
|
children=[
|
|
# a box for holding both the "summary" label and the "close" button
|
|
Box(
|
|
orientation="h",
|
|
children=[
|
|
Label(
|
|
label=self._notification.summary,
|
|
ellipsization="middle",
|
|
)
|
|
.build()
|
|
.add_style_class("summary")
|
|
.unwrap(),
|
|
],
|
|
h_expand=True,
|
|
v_expand=True,
|
|
)
|
|
# add the "close" button
|
|
.build(
|
|
lambda box, _: box.pack_end(
|
|
Button(
|
|
image=Image(
|
|
icon_name="close-symbolic",
|
|
icon_size=18,
|
|
),
|
|
v_align="center",
|
|
h_align="end",
|
|
on_clicked=lambda *_: self._notification.close(),
|
|
),
|
|
False,
|
|
False,
|
|
0,
|
|
)
|
|
),
|
|
Label(
|
|
label=self._notification.body,
|
|
line_wrap="word-char",
|
|
v_align="start",
|
|
h_align="start",
|
|
)
|
|
.build()
|
|
.add_style_class("body")
|
|
.unwrap(),
|
|
],
|
|
h_expand=True,
|
|
v_expand=True,
|
|
)
|
|
)
|
|
|
|
self.add(body_container)
|
|
|
|
if actions := self._notification.actions:
|
|
self.add(
|
|
Box(
|
|
spacing=4,
|
|
orientation="h",
|
|
children=[
|
|
Button(
|
|
h_expand=True,
|
|
v_expand=True,
|
|
label=action.label,
|
|
on_clicked=lambda *_, action=action: action.invoke(),
|
|
)
|
|
for action in actions
|
|
],
|
|
)
|
|
)
|
|
|
|
# destroy this widget once the notification is closed
|
|
self._notification.connect(
|
|
"closed",
|
|
lambda *_: (
|
|
parent.remove(self) if (parent := self.get_parent()) else None, # type: ignore
|
|
self.destroy(),
|
|
),
|
|
)
|
|
|
|
# automatically close the notification after the timeout period
|
|
invoke_repeater(
|
|
NOTIFICATION_TIMEOUT,
|
|
lambda: self._notification.close("expired"),
|
|
initial_call=False,
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
app = Application(
|
|
"notifications",
|
|
WaylandWindow(
|
|
margin="8px 8px 8px 8px",
|
|
anchor="top right",
|
|
child=Box(
|
|
size=2, # so it's not ignored by the compositor
|
|
spacing=4,
|
|
orientation="v",
|
|
).build(
|
|
lambda viewport, _: Notifications(
|
|
on_notification_added=lambda notifs_service, nid: viewport.add(
|
|
NotificationWidget(
|
|
cast(
|
|
Notification,
|
|
notifs_service.get_notification_from_id(nid),
|
|
)
|
|
)
|
|
)
|
|
)
|
|
),
|
|
visible=True,
|
|
all_visible=True,
|
|
),
|
|
)
|
|
|
|
app.set_stylesheet_from_file(get_relative_path("./style.css"))
|
|
|
|
app.run()
|