nixos/modules/home/ags/app/widget/Notifications.tsx

129 lines
4.6 KiB
TypeScript

import GLib from "gi://GLib"
import { App, Astal, Gdk, Gtk } from "astal/gtk3"
import Notifd from "gi://AstalNotifd"
import { bind } from "astal"
import { MAX_TIMEOUT } from "../notif-config"
const notifd = Notifd.get_default()
notifd.default_timeout = MAX_TIMEOUT
// notifications restored from the daemon's cache on startup are stale
for (const n of notifd.notifications) {
n.dismiss()
}
// the daemon honors app-requested timeouts, so enforce our own cap for
// notifications that ask for more (or none at all). critical notifications
// are exempt and stay until dismissed.
notifd.connect("notified", (_, id: number) => {
const n = notifd.get_notification(id)
if (!n || n.urgency === Notifd.Urgency.CRITICAL) return
if (n.expire_timeout <= 0 || n.expire_timeout > MAX_TIMEOUT) {
GLib.timeout_add(GLib.PRIORITY_DEFAULT, MAX_TIMEOUT, () => {
if (notifd.notifications.includes(n)) n.dismiss()
return GLib.SOURCE_REMOVE
})
}
})
function formatTime(n: Notifd.Notification) {
return GLib.DateTime.new_from_unix_local(n.time).format("%H:%M")!
}
export function NotificationCard(n: Notifd.Notification) {
const critical = n.urgency === Notifd.Urgency.CRITICAL
return (
<button
className={`notif${critical ? " critical" : ""}`}
onClicked={() => n.dismiss()}
>
<box vertical>
<box className="notif-header">
<icon
pixelSize={16}
icon={n.app_icon || "preferences-system-notifications-symbolic"}
/>
<label
className="notif-app-name"
label={n.app_name || "Notification"}
halign={Gtk.Align.START}
hexpand
/>
<label className="notif-time" label={formatTime(n)} />
<button
className="notif-close"
onClicked={() => n.dismiss()}
>
<icon icon="window-close-symbolic" pixelSize={12} />
</button>
</box>
<box className="notif-content">
{n.image !== "" && (
<icon className="notif-image" pixelSize={36} icon={n.image} />
)}
<box vertical hexpand>
<label
className="notif-summary"
label={n.summary}
halign={Gtk.Align.START}
visible={n.summary !== ""}
/>
<label
className="notif-text"
label={n.body}
wrap
halign={Gtk.Align.START}
visible={n.body !== ""}
/>
</box>
</box>
{n.actions.filter((a) => a.label).length > 0 && (
<box className="notif-actions" homogeneous spacing={4}>
{n.actions
.filter((a) => a.label)
.map((a) => (
<button
className="notif-action"
onClicked={() => a.invoke()}
>
<label label={a.label} />
</button>
))}
</box>
)}
</box>
</button>
)
}
export default function Notifications(gdkmonitor: Gdk.Monitor) {
const { TOP, RIGHT } = Astal.WindowAnchor
return (
<window
className="popup-window"
namespace="ags-notifications"
application={App}
gdkmonitor={gdkmonitor}
layer={Astal.Layer.OVERLAY}
anchor={TOP | RIGHT}
exclusivity={Astal.Exclusivity.IGNORE}
keymode={Astal.Keymode.NONE}
marginTop={54}
marginRight={8}
visible={bind(notifd, "notifications").as((ns) => ns.length > 0)}
>
<box className="notifications" vertical spacing={8} widthRequest={360}>
{bind(notifd, "notifications").as((ns) =>
[...ns]
.sort((a, b) => b.time - a.time)
.map((n) => NotificationCard(n)),
)}
</box>
</window>
)
}