Add AGS notification support
This commit is contained in:
parent
3963d77466
commit
135b6cb482
|
|
@ -1,10 +1,12 @@
|
||||||
import { App } from "astal/gtk3"
|
import { App } from "astal/gtk3"
|
||||||
import style from "./style.scss"
|
import style from "./style.scss"
|
||||||
import Bar from "./widget/Bar"
|
import Bar from "./widget/Bar"
|
||||||
|
import Notifications from "./widget/Notifications"
|
||||||
|
|
||||||
App.start({
|
App.start({
|
||||||
css: style,
|
css: style,
|
||||||
main() {
|
main() {
|
||||||
App.get_monitors().map(Bar)
|
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 {
|
.power-row:hover {
|
||||||
background: transparentize($base05, 0.88);
|
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 =
|
astalDeps =
|
||||||
with pkgs.astal;
|
with pkgs.astal;
|
||||||
[
|
[
|
||||||
|
|
@ -48,6 +52,7 @@ let
|
||||||
network
|
network
|
||||||
battery
|
battery
|
||||||
wireplumber
|
wireplumber
|
||||||
|
notifd
|
||||||
]
|
]
|
||||||
++ [ pkgs.networkmanager ];
|
++ [ pkgs.networkmanager ];
|
||||||
|
|
||||||
|
|
@ -59,6 +64,7 @@ let
|
||||||
dependencies = astalDeps;
|
dependencies = astalDeps;
|
||||||
postUnpack = ''
|
postUnpack = ''
|
||||||
cp ${palette} "$sourceRoot/palette.scss"
|
cp ${palette} "$sourceRoot/palette.scss"
|
||||||
|
cp ${notifConfig} "$sourceRoot/notif-config.ts"
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
|
|
|
||||||
|
|
@ -9,5 +9,14 @@ with lib;
|
||||||
default = false;
|
default = false;
|
||||||
description = "Enable the AGS status bar";
|
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