[AGS] Add new notifications setup

This commit is contained in:
2025-04-26 09:49:03 +02:00
parent e19a1179d5
commit f2bdddb9b6
6 changed files with 279 additions and 0 deletions

View File

@@ -0,0 +1,6 @@
# Source
This has been copied from [matshell](https://github.com/Neurarian/matshell)
It is not yet used, as it has not been adapted yet to feature a notification history.
Potentially, a notification centre will be added to make this here work better. Styling is also missing

View File

@@ -0,0 +1,32 @@
import { Astal } from "astal/gtk4";
import Notifd from "gi://AstalNotifd";
import Hyprland from "gi://AstalHyprland";
import { bind } from "astal";
import { NotificationWidget } from "./modules/Notification";
import { hyprToGdk } from "../../util/hyprland";
export default function Notifications() {
const notifd = Notifd.get_default();
const hyprland = Hyprland.get_default();
const { TOP, RIGHT } = Astal.WindowAnchor;
return (
<window
name="notifications"
gdkmonitor={bind(hyprland, "focusedMonitor").as(
(focused: Hyprland.Monitor) => hyprToGdk(focused),
)}
anchor={TOP | RIGHT}
visible={bind(notifd, "notifications").as(
(notifications) => notifications.length > 0,
)}
child={
<box vertical={true} cssClasses={["notifications"]}>
{bind(notifd, "notifications").as((notifications) =>
notifications.map((n) => <NotificationWidget notification={n} />),
)}
</box>
}
/>
);
}

View File

@@ -0,0 +1,25 @@
import { Gtk } from "astal/gtk4";
import Notifd from "gi://AstalNotifd";
import { fileExists, isIcon } from "../../../util/notifd";
export function NotificationIcon(notification: Notifd.Notification) {
if (notification.image || notification.appIcon || notification.desktopEntry) {
const icon = notification.image || notification.appIcon || notification.desktopEntry;
if (fileExists(icon)) {
return (
<box expand={false} valign={Gtk.Align.CENTER}>
<image file={icon} />
</box>
);
} else if (isIcon(icon)) {
return (
<box expand={false} valign={Gtk.Align.CENTER}>
<image iconName={icon} />
</box>
);
}
}
return null;
}

View File

@@ -0,0 +1,126 @@
import { bind } from "astal";
import { Gtk } from "astal/gtk4";
import Notifd from "gi://AstalNotifd";
import { NotificationIcon } from "./Icon";
import { createTimeoutManager } from "../../../util/notifd";
export function NotificationWidget({
notification,
}: {
notification: Notifd.Notification;
}) {
const { START, CENTER, END } = Gtk.Align;
const actions = notification.actions || [];
const TIMEOUT_DELAY = 3000;
// Keep track of notification validity
const notifd = Notifd.get_default();
const timeoutManager = createTimeoutManager(
() => notification.dismiss(),
TIMEOUT_DELAY,
);
return (
<box
setup={(self) => {
// Set up timeout
timeoutManager.setupTimeout();
const clickGesture = Gtk.GestureClick.new();
clickGesture.set_button(0); // 0 means any button
clickGesture.connect("pressed", (gesture, _) => {
try {
// Get which button was pressed (1=left, 2=middle, 3=right)
const button = gesture.get_current_button();
if (button === 1) {
// PRIMARY/LEFT
if (actions.length > 0) n.invoke(actions[0]);
} else if (button === 2) {
// MIDDLE
notifd.notifications?.forEach((n) => {
n.dismiss();
});
} else if (button === 3) {
// SECONDARY/RIGHT
notification.dismiss();
}
} catch (error) {
console.error(error);
}
});
self.add_controller(clickGesture);
self.connect("unrealize", () => {
timeoutManager.cleanup();
});
}}
onHoverEnter={timeoutManager.handleHover}
onHoverLeave={timeoutManager.handleHoverLost}
vertical
vexpand={false}
cssClasses={["notification", `${urgency(notification)}`]}
name={notification.id.toString()}
>
<box cssClasses={["header"]}>
<label
cssClasses={["app-name"]}
halign={CENTER}
label={bind(notification, "app_name")}
/>
<label
cssClasses={["time"]}
hexpand
halign={END}
label={time(notification.time)}
/>
</box>
<Gtk.Separator />
<box cssClasses={["content"]}>
<box
cssClasses={["thumb"]}
visible={Boolean(NotificationIcon(notification))}
halign={CENTER}
valign={CENTER}
vexpand={true}
>
{NotificationIcon(notification)}
</box>
<box
vertical
cssClasses={["text-content"]}
hexpand={true}
halign={CENTER}
valign={CENTER}
>
<label
cssClasses={["title"]}
valign={CENTER}
wrap={false}
label={bind(notification, "summary")}
/>
{notification.body && (
<label
cssClasses={["body"]}
valign={CENTER}
wrap={true}
maxWidthChars={50}
label={bind(notification, "body")}
/>
)}
</box>
</box>
{actions.length > 0 && (
<box cssClasses={["actions"]}>
{actions.map(({ label, action }) => (
<button
hexpand
cssClasses={["action-button"]}
onClicked={() => notification.invoke(action)}
>
<label label={label} halign={CENTER} hexpand />
</button>
))}
</box>
)}
</box>
);
}