151 lines
5.3 KiB
TypeScript
151 lines
5.3 KiB
TypeScript
import GLib from "gi://GLib"
|
|
import { App, Astal, Gdk, Gtk } from "astal/gtk3"
|
|
import Notifd from "gi://AstalNotifd"
|
|
import { Variable, bind } from "astal"
|
|
import { MAX_TIMEOUT } from "../notif-config"
|
|
|
|
const notifd = Notifd.get_default()
|
|
|
|
export const dndIcon = Variable.derive([bind(notifd, "dontDisturb")], (dnd) =>
|
|
dnd
|
|
? "notifications-disabled-symbolic"
|
|
: "preferences-system-notifications-symbolic",
|
|
)
|
|
|
|
export function DndToggle() {
|
|
return (
|
|
<button
|
|
className={bind(notifd, "dontDisturb").as((v) =>
|
|
v ? "simple-toggle active" : "simple-toggle",
|
|
)}
|
|
onClicked={() => (notifd.dontDisturb = !notifd.dontDisturb)}
|
|
>
|
|
<box spacing={8}>
|
|
<icon className="icon" icon={dndIcon()} />
|
|
<label label="Do Not Disturb" />
|
|
</box>
|
|
</button>
|
|
)
|
|
}
|
|
|
|
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>
|
|
)
|
|
}
|