Add AGS notification support
This commit is contained in:
parent
eef5252e49
commit
92cb3f5dbf
|
|
@ -1,10 +1,12 @@
|
|||
import { App } from "astal/gtk3"
|
||||
import style from "./style.scss"
|
||||
import Bar from "./widget/Bar"
|
||||
import Notifications from "./widget/Notifications"
|
||||
|
||||
App.start({
|
||||
css: style,
|
||||
main() {
|
||||
App.get_monitors().map(Bar)
|
||||
Notifications(App.get_monitors()[0])
|
||||
},
|
||||
})
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
export const MAX_TIMEOUT = 10000
|
||||
|
|
@ -193,3 +193,66 @@
|
|||
.power-row:hover {
|
||||
background: transparentize($base05, 0.88);
|
||||
}
|
||||
|
||||
.notifications {
|
||||
.notif {
|
||||
background: $base01;
|
||||
border-radius: 10px;
|
||||
padding: 8px 12px;
|
||||
color: $base05;
|
||||
|
||||
&:hover {
|
||||
background: transparentize($base05, 0.88);
|
||||
}
|
||||
|
||||
&.critical {
|
||||
border: 1px solid $base08;
|
||||
}
|
||||
}
|
||||
|
||||
.notif-header {
|
||||
margin-bottom: 4px;
|
||||
|
||||
.notif-app-name {
|
||||
font-weight: bold;
|
||||
margin: 0 6px;
|
||||
}
|
||||
|
||||
.notif-time {
|
||||
font-size: 0.85em;
|
||||
opacity: 0.7;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.notif-close {
|
||||
border-radius: 6px;
|
||||
padding: 0 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.notif-content {
|
||||
.notif-image {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.notif-summary {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.notif-text {
|
||||
font-size: 0.9em;
|
||||
opacity: 0.85;
|
||||
}
|
||||
}
|
||||
|
||||
.notif-action {
|
||||
background: $base02;
|
||||
border-radius: 6px;
|
||||
margin-top: 6px;
|
||||
padding: 2px 8px;
|
||||
|
||||
&:hover {
|
||||
background: $base03;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,128 @@
|
|||
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")!
|
||||
}
|
||||
|
||||
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={48}
|
||||
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>
|
||||
)
|
||||
}
|
||||
|
|
@ -39,6 +39,10 @@ let
|
|||
)
|
||||
);
|
||||
|
||||
notifConfig = pkgs.writeText "notif-config.ts" ''
|
||||
export const MAX_TIMEOUT = ${toString cfg.timeout};
|
||||
'';
|
||||
|
||||
astalDeps =
|
||||
with pkgs.astal;
|
||||
[
|
||||
|
|
@ -48,6 +52,7 @@ let
|
|||
network
|
||||
battery
|
||||
wireplumber
|
||||
notifd
|
||||
]
|
||||
++ [ pkgs.networkmanager ];
|
||||
|
||||
|
|
@ -59,6 +64,7 @@ let
|
|||
dependencies = astalDeps;
|
||||
postUnpack = ''
|
||||
cp ${palette} "$sourceRoot/palette.scss"
|
||||
cp ${notifConfig} "$sourceRoot/notif-config.ts"
|
||||
'';
|
||||
};
|
||||
in
|
||||
|
|
|
|||
|
|
@ -9,5 +9,14 @@ with lib;
|
|||
default = false;
|
||||
description = "Enable the AGS status bar";
|
||||
};
|
||||
|
||||
timeout = mkOption {
|
||||
type = types.ints.positive;
|
||||
default = 10000;
|
||||
description = ''
|
||||
Maximum duration notification popups stay on screen, in milliseconds.
|
||||
Critical notifications are exempt and stay until dismissed.
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue