Format and import changes

This commit is contained in:
Thomas Avé 2025-09-02 13:14:26 +07:00
parent e4646fc2d8
commit a22aeed71a
6 changed files with 245 additions and 188 deletions

View File

@ -1,30 +1,31 @@
import { Astal, Gdk } from "ags/gtk4"; import { Astal, Gdk, Gtk } from "ags/gtk4";
import app from "ags/gtk4/app" import app from "ags/gtk4/app";
import Gtk from "gi://Gtk?version=4.0" import { createBinding, createState, For, With, Accessor } from "ags";
import { createBinding, createState, For, With, Accessor } from "ags" import { createPoll } from "ags/time";
import { createPoll } from "ags/time"
import Tray from "gi://AstalTray"; import Tray from "gi://AstalTray";
import { execAsync } from "ags/process" import { execAsync } from "ags/process";
import Hyprland from "gi://AstalHyprland"; import Hyprland from "gi://AstalHyprland";
import { getIconName } from "./utils"; import { getIconName } from "./utils";
import Wp from "gi://AstalWp" import Wp from "gi://AstalWp";
import Battery from "gi://AstalBattery" import Battery from "gi://AstalBattery";
import GLib from "gi://GLib"; import GLib from "gi://GLib";
const battery = Battery.get_default() const battery = Battery.get_default();
const sensorsAvailable = await execAsync(['sensors']).then(() => true).catch(() => false); const sensorsAvailable = await execAsync(["sensors"])
.then(() => true)
.catch(() => false);
const wirePlumber = Wp.get_default(); const wirePlumber = Wp.get_default();
function SysTray(): JSX.Element { function SysTray(): JSX.Element {
const tray = Tray.get_default(); const tray = Tray.get_default();
let items = createBinding(tray, "items"); let items = createBinding(tray, "items");
const init = (btn: Gtk.MenuButton, item: Tray.TrayItem) => { const init = (btn: Gtk.MenuButton, item: Tray.TrayItem) => {
btn.menuModel = item.menuModel btn.menuModel = item.menuModel;
btn.insert_action_group("dbusmenu", item.actionGroup) btn.insert_action_group("dbusmenu", item.actionGroup);
item.connect("notify::action-group", () => { item.connect("notify::action-group", () => {
btn.insert_action_group("dbusmenu", item.actionGroup) btn.insert_action_group("dbusmenu", item.actionGroup);
}) });
} };
return ( return (
<box> <box>
<For each={items}> <For each={items}>
@ -35,7 +36,8 @@ function SysTray(): JSX.Element {
$={(self) => init(self, item)} $={(self) => init(self, item)}
class="systray" class="systray"
tooltipMarkup={createBinding(item, "tooltipMarkup")} tooltipMarkup={createBinding(item, "tooltipMarkup")}
menuModel={createBinding(item, "menuModel")}> menuModel={createBinding(item, "menuModel")}
>
<image gicon={item.gicon} class="systray-item" /> <image gicon={item.gicon} class="systray-item" />
</menubutton> </menubutton>
); );
@ -62,74 +64,74 @@ function Center(): JSX.Element {
} }
function Date({ format = "%Y-%m-%d" }): JSX.Element { function Date({ format = "%Y-%m-%d" }): JSX.Element {
const time = createPoll<string>("", 60000, () => GLib.DateTime.new_now_local().format(format)!) const time = createPoll<string>(
return <button "",
class="item" 60000,
label={time} () => GLib.DateTime.new_now_local().format(format)!,
onClicked={() => execAsync(['gnome-calendar'])} );
/> return (
<button
class="item"
label={time}
onClicked={() => execAsync(["gnome-calendar"])}
/>
);
} }
function Time({ format = "%H:%M:%S" }): JSX.Element { function Time({ format = "%H:%M:%S" }): JSX.Element {
const time = createPoll<string>("", 1000, () => GLib.DateTime.new_now_local().format(format)!) const time = createPoll<string>(
return <label "",
class="item blue" 1000,
label={time} () => GLib.DateTime.new_now_local().format(format)!,
/> );
return <label class="item blue" label={time} />;
} }
function Temp(): JSX.Element { function Temp(): JSX.Element {
let [label, _setlabel] = createState<string>("N/A"); let [label, _setlabel] = createState<string>("N/A");
if (sensorsAvailable) { if (sensorsAvailable) {
label = createPoll<string>("", 3000, 'sensors', out => { label = createPoll<string>("", 3000, "sensors", (out) => {
const match = out.split('\n').find(line => line.includes('Tctl') || line.includes('Package'))?.match(/[0-9.]*°C/); const match = out
.split("\n")
.find((line) => line.includes("Tctl") || line.includes("Package"))
?.match(/[0-9.]*°C/);
return match ? match[0] : "N/A"; return match ? match[0] : "N/A";
}) });
} }
return <label return <label class="item blue" label={label} />;
class="item blue"
label={label}
/>
} }
function Memory(): JSX.Element { function Memory(): JSX.Element {
const memory = createPoll<string>("", 2000, "free", out => { const memory = createPoll<string>("", 2000, "free", (out) => {
const line = out.split('\n').find(line => line.includes('Mem:')); const line = out.split("\n").find((line) => line.includes("Mem:"));
if (!line) return "N/A"; if (!line) return "N/A";
const split = line.split(/\s+/).map(Number); const split = line.split(/\s+/).map(Number);
return (split[2] / 1000000).toFixed(2) + "GB"; return (split[2] / 1000000).toFixed(2) + "GB";
}); });
return <label return <label class="item blue" label={memory} />;
class="item blue"
label={memory}
/>
} }
function ClockSpeed(): JSX.Element { function ClockSpeed(): JSX.Element {
const command = 'bash -c "cat /proc/cpuinfo | grep \\"MHz\\" | awk \'{print \\$4}\' | sort -n | tail -1 | awk \'{printf \\"%.2fGHz\\", \\$1/1000}\'"'; const command =
const speed = createPoll<string>("", 1000, command, out => out) 'bash -c "cat /proc/cpuinfo | grep \\"MHz\\" | awk \'{print \\$4}\' | sort -n | tail -1 | awk \'{printf \\"%.2fGHz\\", \\$1/1000}\'"';
return <label const speed = createPoll<string>("", 1000, command, (out) => out);
class="item" return <label class="item" label={speed} />;
label={speed}
/>
} }
function CPU(): JSX.Element { function CPU(): JSX.Element {
const usage = createPoll<string>("", 2000, "top -b -n 1", out => { const usage = createPoll<string>("", 2000, "top -b -n 1", (out) => {
const line = out.split("\n").find(line => line.includes('Cpu(s)')); const line = out.split("\n").find((line) => line.includes("Cpu(s)"));
if (!line) return "N/A"; if (!line) return "N/A";
return line.split(/\s+/)[1].replace(',', '.').toString() + "%"; return line.split(/\s+/)[1].replace(",", ".").toString() + "%";
}); });
return <box class="item"> return (
<image iconName="speedometer" css="margin-right: 0.7em;" /> <box class="item">
<label <image iconName="speedometer" css="margin-right: 0.7em;" />
label={usage} <label label={usage} />
/> </box>
</box> );
} }
function Right() { function Right() {
return ( return (
<box class="right" hexpand halign={Gtk.Align.END} spacing={6}> <box class="right" hexpand halign={Gtk.Align.END} spacing={6}>
@ -148,20 +150,34 @@ function Right() {
function BatteryIcon(): JSX.Element { function BatteryIcon(): JSX.Element {
if (battery.get_state() == 0) return <box />; if (battery.get_state() == 0) return <box />;
let batteryPercentage = createBinding(battery, "percentage"); let batteryPercentage = createBinding(battery, "percentage");
return <button class="battery-item" onClicked={() => execAsync(['gnome-power-statistics'])}> return (
<box> <button
<With value={batteryPercentage}> class="battery-item"
{(percentage) => { onClicked={() => execAsync(["gnome-power-statistics"])}
const thresholds = [...Array(11).keys()].map(i => i * 10); >
const icon = thresholds.find(threshold => threshold >= percentage * 100) <box>
const charging_name = battery.percentage >= 0.99 ? "charged" : "charging" <With value={batteryPercentage}>
return <image {(percentage) => {
iconName={battery.charging ? `battery-level-${icon}-${charging_name}-symbolic` : `battery-level-${icon}-symbolic`} const thresholds = [...Array(11).keys()].map((i) => i * 10);
/> const icon = thresholds.find(
}} (threshold) => threshold >= percentage * 100,
</With> );
</box> const charging_name =
</button> battery.percentage >= 0.99 ? "charged" : "charging";
return (
<image
iconName={
battery.charging
? `battery-level-${icon}-${charging_name}-symbolic`
: `battery-level-${icon}-symbolic`
}
/>
);
}}
</With>
</box>
</button>
);
} }
function Icons() { function Icons() {
@ -170,7 +186,7 @@ function Icons() {
<SysTray /> <SysTray />
<BatteryIcon /> <BatteryIcon />
</box> </box>
) );
} }
function Volume(): JSX.Element { function Volume(): JSX.Element {
@ -178,42 +194,51 @@ function Volume(): JSX.Element {
const audio = wirePlumber.audio; const audio = wirePlumber.audio;
const icon = createBinding(audio.default_speaker, "volume").as((volume) => { const icon = createBinding(audio.default_speaker, "volume").as((volume) => {
const vol = volume * 100 const vol = volume * 100;
const icon = [ const icon = [
[101, 'overamplified'], [101, "overamplified"],
[67, 'high'], [67, "high"],
[34, 'medium'], [34, "medium"],
[1, 'low'], [1, "low"],
[0, 'muted'], [0, "muted"],
].find(([threshold]) => Number(threshold) <= vol)?.[1] ].find(([threshold]) => Number(threshold) <= vol)?.[1];
return `audio-volume-${icon}-symbolic` return `audio-volume-${icon}-symbolic`;
}); });
const css = createBinding(audio.default_speaker, "mute").as((mute) => { const css = createBinding(audio.default_speaker, "mute").as((mute) => {
return mute ? "margin-left:0;" : "margin-left: 0.7em;" return mute ? "margin-left:0;" : "margin-left: 0.7em;";
}); });
let volume = createBinding(audio.default_speaker, "volume"); let volume = createBinding(audio.default_speaker, "volume");
let mute = createBinding(audio.default_speaker, "mute"); let mute = createBinding(audio.default_speaker, "mute");
return ( return (
<button class="item blue" onClicked={() => audio.default_speaker.mute = !audio.default_speaker.mute}> <button
class="item blue"
onClicked={() =>
(audio.default_speaker.mute = !audio.default_speaker.mute)
}
>
<box> <box>
<image iconName={icon} /> <image iconName={icon} />
<With value={volume}> <With value={volume}>
{(vol) => <box> {(vol) => (
<With value={mute}> <box>
{(muted) => { <With value={mute}>
return ( {(muted) => {
<label label={muted ? "" : `${Math.floor(vol * 100)}%`} css={css} /> return (
) <label
}} label={muted ? "" : `${Math.floor(vol * 100)}%`}
</With> css={css}
</box>} />
);
}}
</With>
</box>
)}
</With> </With>
</box> </box>
</button> </button>
); );
} }
function Workspaces(): JSX.Element { function Workspaces(): JSX.Element {
const hyprland = Hyprland.get_default(); const hyprland = Hyprland.get_default();
let workspaces = createBinding(hyprland, "workspaces"); let workspaces = createBinding(hyprland, "workspaces");
@ -224,14 +249,24 @@ function Workspaces(): JSX.Element {
<box> <box>
<With value={createBinding(hyprland, "focusedMonitor")}> <With value={createBinding(hyprland, "focusedMonitor")}>
{(fm: Hyprland.Monitor) => { {(fm: Hyprland.Monitor) => {
let filtered_wss = new Accessor(() => wss.sort((a, b) => a.id - b.id) let filtered_wss = new Accessor(() =>
.filter(ws => ws && ws.get_monitor() && ws.get_monitor().get_id() === fm.get_id())) wss
.sort((a, b) => a.id - b.id)
.filter(
(ws) =>
ws &&
ws.get_monitor() &&
ws.get_monitor().get_id() === fm.get_id(),
),
);
return ( return (
<box> <box>
<For each={filtered_wss}> <For each={filtered_wss}>
{(ws: Hyprland.Workspace, _index) => ( {(ws: Hyprland.Workspace, _index) => (
<button <button
class={createBinding(hyprland, "focusedWorkspace").as((fw) => ws === fw ? "focused" : "",)} class={createBinding(hyprland, "focusedWorkspace").as(
(fw) => (ws === fw ? "focused" : ""),
)}
onClicked={() => ws.focus()} onClicked={() => ws.focus()}
> >
{`${ws.id}`.slice(-1)} {`${ws.id}`.slice(-1)}
@ -239,7 +274,7 @@ function Workspaces(): JSX.Element {
)} )}
</For> </For>
</box> </box>
) );
}} }}
</With> </With>
</box> </box>
@ -250,12 +285,12 @@ function Workspaces(): JSX.Element {
} }
function shorten(title: string) { function shorten(title: string) {
return title.length > 40 ? title.slice(0, 20) + "..." : title return title.length > 40 ? title.slice(0, 20) + "..." : title;
} }
function Clients(): JSX.Element { function Clients(): JSX.Element {
const hyprland = Hyprland.get_default(); const hyprland = Hyprland.get_default();
let clients = createBinding(hyprland, "clients") let clients = createBinding(hyprland, "clients");
return ( return (
<box> <box>
<With value={createBinding(hyprland, "focusedWorkspace")}> <With value={createBinding(hyprland, "focusedWorkspace")}>
@ -263,36 +298,44 @@ function Clients(): JSX.Element {
<box class="clients"> <box class="clients">
<With value={clients}> <With value={clients}>
{(cls: Array<Hyprland.Client>) => { {(cls: Array<Hyprland.Client>) => {
let filtered_clients = new Accessor(() => cls let filtered_clients = new Accessor(() =>
.sort((a, b) => a.pid - b.pid) cls
.filter(cl => !cl.title.includes("rofi")) .sort((a, b) => a.pid - b.pid)
.filter(cl => fw && cl.get_workspace().get_id() === fw.get_id())) .filter((cl) => !cl.title.includes("rofi"))
.filter(
(cl) => fw && cl.get_workspace().get_id() === fw.get_id(),
),
);
return ( return (
<box> <box>
<For each={filtered_clients}> <For each={filtered_clients}>
{(cl: Hyprland.Client, _index) => ( {(cl: Hyprland.Client, _index) => (
<box <box
class={createBinding(hyprland, "focusedClient").as(a => a && a.address === cl.address ? "focused" : "unfocused")} class={createBinding(hyprland, "focusedClient").as(
(a) =>
a && a.address === cl.address
? "focused"
: "unfocused",
)}
> >
<image <image iconName={getIconName(cl)} class="app-icon" />
iconName={getIconName(cl)} <label
class="app-icon" label={createBinding(cl, "title").as((title) =>
shorten(title),
)}
/> />
<label label={createBinding(cl, 'title').as(title => shorten(title))} />
</box> </box>
)} )}
</For> </For>
</box> </box>
) );
} }}
}
</With> </With>
</box> </box>
) )}
} </With>
</With > </box>
</box >
); );
} }
@ -304,15 +347,20 @@ export default function Bar(gdkmonitor: Gdk.Monitor, scaleFactor: number = 1) {
gdkmonitor={gdkmonitor} gdkmonitor={gdkmonitor}
css={"font-size: " + scaleFactor + "em;"} css={"font-size: " + scaleFactor + "em;"}
exclusivity={Astal.Exclusivity.EXCLUSIVE} exclusivity={Astal.Exclusivity.EXCLUSIVE}
anchor={Astal.WindowAnchor.TOP | Astal.WindowAnchor.LEFT | Astal.WindowAnchor.RIGHT} anchor={
Astal.WindowAnchor.TOP |
Astal.WindowAnchor.LEFT |
Astal.WindowAnchor.RIGHT
}
application={app} application={app}
class="Bar" class="Bar"
name="top-bar" > name="top-bar"
>
<centerbox class="window-box"> <centerbox class="window-box">
<Left $type="start" /> <Left $type="start" />
<Center $type="center" /> <Center $type="center" />
<Right $type="end" /> <Right $type="end" />
</centerbox> </centerbox>
</window > </window>
) );
} }

View File

@ -1,35 +1,40 @@
import app from "ags/gtk4/app" import app from "ags/gtk4/app";
import style from "./style.scss" import style from "./style.scss";
import Bar from "./Bar" import Bar from "./Bar";
import Hyprland from "gi://AstalHyprland"; import Hyprland from "gi://AstalHyprland";
import NotificationPopups from "./notifications/NotificationPopups" import NotificationPopups from "./notifications/NotificationPopups";
const hyprland = Hyprland.get_default(); const hyprland = Hyprland.get_default();
function find_main_monitor(): Hyprland.Monitor { function find_main_monitor(): Hyprland.Monitor {
let monitors = hyprland.get_monitors() let monitors = hyprland.get_monitors();
for (let j = 0; j < monitors.length; j++) { for (let j = 0; j < monitors.length; j++) {
for (const monitor of ["eDP", "DP", "HDMI-A"]) { for (const monitor of ["eDP", "DP", "HDMI-A"]) {
for (let i = 0; i < monitors.length; i++) { for (let i = 0; i < monitors.length; i++) {
console.log("Checking monitor:", monitors[i].get_name(), "against", monitor + "-" + j) console.log(
"Checking monitor:",
monitors[i].get_name(),
"against",
monitor + "-" + j,
);
if (monitors[i].get_name() == monitor + "-" + j) { if (monitors[i].get_name() == monitor + "-" + j) {
return monitors[i] return monitors[i];
} }
} }
} }
} }
return monitors[0] return monitors[0];
} }
function register_windows(monitor: Hyprland.Monitor) { function register_windows(monitor: Hyprland.Monitor) {
let gtkMonitor = app.get_monitors()[0] let gtkMonitor = app.get_monitors()[0];
let scale = (monitor.get_width() >= 3000) ? 1.2 : 1 let scale = monitor.get_width() >= 3000 ? 1.2 : 1;
Bar(gtkMonitor, scale) Bar(gtkMonitor, scale);
NotificationPopups() NotificationPopups();
} }
function switch_to_best_monitor() { function switch_to_best_monitor() {
let mainMonitor = find_main_monitor() let mainMonitor = find_main_monitor();
for (var wd of app.get_windows()) { for (var wd of app.get_windows()) {
wd.destroy(); wd.destroy();
} }
@ -37,17 +42,19 @@ function switch_to_best_monitor() {
} }
hyprland.connect("monitor-added", (_, _monitor: Hyprland.Monitor) => { hyprland.connect("monitor-added", (_, _monitor: Hyprland.Monitor) => {
switch_to_best_monitor() switch_to_best_monitor();
}) });
hyprland.connect("monitor-removed", () => { hyprland.connect("monitor-removed", () => {
switch_to_best_monitor() switch_to_best_monitor();
}) });
console.log("Trying to find the best monitor");
app.start({ app.start({
css: style, css: style,
iconTheme: "Papirus", iconTheme: "Papirus",
main() { main() {
switch_to_best_monitor() switch_to_best_monitor();
}, },
}) });

View File

@ -1,21 +1,21 @@
declare const SRC: string declare const SRC: string
declare module "inline:*" { declare module "inline:*" {
const content: string const content: string
export default content export default content
} }
declare module "*.scss" { declare module "*.scss" {
const content: string const content: string
export default content export default content
} }
declare module "*.blp" { declare module "*.blp" {
const content: string const content: string
export default content export default content
} }
declare module "*.css" { declare module "*.css" {
const content: string const content: string
export default content export default content
} }

View File

@ -1,34 +1,33 @@
import Gtk from "gi://Gtk?version=4.0" import { Gtk, Gdk } from "ags/gtk4";
import Gdk from "gi://Gdk?version=4.0" import Adw from "gi://Adw";
import Adw from "gi://Adw" import GLib from "gi://GLib";
import GLib from "gi://GLib" import AstalNotifd from "gi://AstalNotifd";
import AstalNotifd from "gi://AstalNotifd" import Pango from "gi://Pango";
import Pango from "gi://Pango" import { timeout } from "ags/time";
import { timeout } from "ags/time"
function isIcon(icon?: string | null) { function isIcon(icon?: string | null) {
const iconTheme = Gtk.IconTheme.get_for_display(Gdk.Display.get_default()!) const iconTheme = Gtk.IconTheme.get_for_display(Gdk.Display.get_default()!);
return icon && iconTheme.has_icon(icon) return icon && iconTheme.has_icon(icon);
} }
function fileExists(path: string) { function fileExists(path: string) {
return GLib.file_test(path, GLib.FileTest.EXISTS) return GLib.file_test(path, GLib.FileTest.EXISTS);
} }
function time(time: number, format = "%H:%M") { function time(time: number, format = "%H:%M") {
return GLib.DateTime.new_from_unix_local(time).format(format)! return GLib.DateTime.new_from_unix_local(time).format(format)!;
} }
function urgency(n: AstalNotifd.Notification) { function urgency(n: AstalNotifd.Notification) {
const { LOW, NORMAL, CRITICAL } = AstalNotifd.Urgency const { LOW, NORMAL, CRITICAL } = AstalNotifd.Urgency;
switch (n.urgency) { switch (n.urgency) {
case LOW: case LOW:
return "low" return "low";
case CRITICAL: case CRITICAL:
return "critical" return "critical";
case NORMAL: case NORMAL:
default: default:
return "normal" return "normal";
} }
} }
@ -36,12 +35,12 @@ export default function Notification({
notification: n, notification: n,
onHoverLost, onHoverLost,
}: { }: {
notification: AstalNotifd.Notification notification: AstalNotifd.Notification;
onHoverLost: () => void onHoverLost: () => void;
}) { }) {
const timer = timeout(3000, () => { const timer = timeout(3000, () => {
onHoverLost() onHoverLost();
}) });
return ( return (
<Adw.Clamp maximumSize={400}> <Adw.Clamp maximumSize={400}>
<box <box
@ -49,7 +48,10 @@ export default function Notification({
class={`Notification ${urgency(n)}`} class={`Notification ${urgency(n)}`}
orientation={Gtk.Orientation.VERTICAL} orientation={Gtk.Orientation.VERTICAL}
> >
<Gtk.EventControllerMotion onEnter={() => timer.cancel()} onLeave={onHoverLost} /> <Gtk.EventControllerMotion
onEnter={() => timer.cancel()}
onLeave={onHoverLost}
/>
<box class="header"> <box class="header">
{(n.appIcon || isIcon(n.desktopEntry)) && ( {(n.appIcon || isIcon(n.desktopEntry)) && (
<image <image
@ -120,5 +122,5 @@ export default function Notification({
)} )}
</box> </box>
</Adw.Clamp> </Adw.Clamp>
) );
} }

View File

@ -1,39 +1,39 @@
import app from "ags/gtk4/app" import app from "ags/gtk4/app";
import { Astal, Gtk } from "ags/gtk4" import { Astal, Gtk } from "ags/gtk4";
import AstalNotifd from "gi://AstalNotifd" import AstalNotifd from "gi://AstalNotifd";
import Notification from "./Notification" import Notification from "./Notification";
import { createBinding, For, createState, onCleanup } from "ags" import { createBinding, For, createState, onCleanup } from "ags";
export default function NotificationPopups() { export default function NotificationPopups() {
const monitors = createBinding(app, "monitors") const monitors = createBinding(app, "monitors");
const notifd = AstalNotifd.get_default() const notifd = AstalNotifd.get_default();
const [notifications, setNotifications] = createState( const [notifications, setNotifications] = createState(
new Array<AstalNotifd.Notification>(), new Array<AstalNotifd.Notification>(),
) );
const notifiedHandler = notifd.connect("notified", (_, id, replaced) => { const notifiedHandler = notifd.connect("notified", (_, id, replaced) => {
const notification = notifd.get_notification(id) const notification = notifd.get_notification(id);
if (replaced && notifications.get().some(n => n.id === id)) { if (replaced && notifications.get().some((n) => n.id === id)) {
setNotifications((ns) => ns.map((n) => (n.id === id ? notification : n))) setNotifications((ns) => ns.map((n) => (n.id === id ? notification : n)));
} else { } else {
setNotifications((ns) => [notification, ...ns]) setNotifications((ns) => [notification, ...ns]);
} }
}) });
const resolvedHandler = notifd.connect("resolved", (_, id) => { const resolvedHandler = notifd.connect("resolved", (_, id) => {
setNotifications((ns) => ns.filter((n) => n.id !== id)) setNotifications((ns) => ns.filter((n) => n.id !== id));
}) });
// technically, we don't need to cleanup because in this example this is a root component // technically, we don't need to cleanup because in this example this is a root component
// and this cleanup function is only called when the program exits, but exiting will cleanup either way // and this cleanup function is only called when the program exits, but exiting will cleanup either way
// but it's here to remind you that you should not forget to cleanup signal connections // but it's here to remind you that you should not forget to cleanup signal connections
onCleanup(() => { onCleanup(() => {
notifd.disconnect(notifiedHandler) notifd.disconnect(notifiedHandler);
notifd.disconnect(resolvedHandler) notifd.disconnect(resolvedHandler);
}) });
return ( return (
<For each={monitors} cleanup={(win) => (win as Gtk.Window).destroy()}> <For each={monitors} cleanup={(win) => (win as Gtk.Window).destroy()}>
@ -61,5 +61,5 @@ export default function NotificationPopups() {
</window> </window>
)} )}
</For> </For>
) );
} }

View File

@ -1,5 +1,5 @@
{ {
"dependencies": { "dependencies": {
"ags": "*" "ags": "*"
} }
} }