import { Astal, Gdk, Gtk } from "ags/gtk4"; import app from "ags/gtk4/app"; import { createBinding, createState, For, With, Accessor } from "ags"; import { createPoll } from "ags/time"; import { subprocess, execAsync } from "ags/process"; import Tray from "gi://AstalTray"; import { getIconName } from "./utils"; import Wp from "gi://AstalWp"; import Battery from "gi://AstalBattery"; import GLib from "gi://GLib"; const battery = Battery.get_default(); const sensorsAvailable = await execAsync(["sensors"]) .then(() => true) .catch(() => false); const wirePlumber = Wp.get_default(); // --- NIRI IPC INTEGRATION --- const [workspaces, setWorkspaces] = createState([]); const [windows, setWindows] = createState([]); async function updateNiriState() { try { const wsOut = await execAsync(["niri", "msg", "-j", "workspaces"]); setWorkspaces(JSON.parse(wsOut) || []); const winOut = await execAsync(["niri", "msg", "-j", "windows"]); setWindows(JSON.parse(winOut) || []); } catch (e) { console.error("Failed to fetch Niri state:", e); } } // Initialize state immediately updateNiriState(); // Subscribe to Niri's native event stream subprocess( ["niri", "msg", "-j", "event-stream"], (line) => { // A state change happened in Niri. Re-fetching guarantees perfect sync updateNiriState(); }, (err) => console.error("Niri event stream error:", err) ); // ---------------------------- function SysTray(): JSX.Element { const tray = Tray.get_default(); let items = createBinding(tray, "items"); const init = (btn: Gtk.MenuButton, item: Tray.TrayItem) => { btn.menuModel = item.menuModel; btn.insert_action_group("dbusmenu", item.actionGroup); item.connect("notify::action-group", () => { btn.insert_action_group("dbusmenu", item.actionGroup); }); }; return ( {(item: Tray.TrayItem) => { if (item.iconThemePath) app.add_icons(item.iconThemePath); return ( init(self, item)} class="systray" tooltipMarkup={createBinding(item, "tooltipMarkup")} menuModel={createBinding(item, "menuModel")} > ); }} ); } function Left({ connector }: { connector: string }): JSX.Element { return ( ); } function Center({ connector }: { connector: string }): JSX.Element { return ( ); } function Date({ format = "%Y-%m-%d" }): JSX.Element { const time = createPoll( "", 60000, () => GLib.DateTime.new_now_local().format(format)!, ); return ( ); } function Icons() { return ( ); } function Volume(): JSX.Element { if (!wirePlumber) return ; const audio = wirePlumber.audio; const icon = createBinding(audio.default_speaker, "volume").as((volume) => { const vol = volume * 100; const icon = [ [101, "overamplified"], [67, "high"], [34, "medium"], [1, "low"], [0, "muted"], ].find(([threshold]) => Number(threshold) <= vol)?.[1]; return `audio-volume-${icon}-symbolic`; }); const css = createBinding(audio.default_speaker, "mute").as((mute) => { return mute ? "margin-left:0;" : "margin-left: 0.7em;"; }); let volume = createBinding(audio.default_speaker, "volume"); let mute = createBinding(audio.default_speaker, "mute"); return ( ); } function Workspaces({ connector }: { connector: string }): JSX.Element { return ( {/* Generate a derived binding by passing a transformation function into the `workspaces` state */} wss.filter((ws) => ws.output === connector) .filter((ws) => ws.is_active || windows().some((w: any) => w.workspace_id === ws.id)) .sort((a, b) => a.idx - b.idx) )}> {(ws: any) => ( )} ); } function shorten(title: string) { return title.length > 40 ? title.slice(0, 20) + "..." : title; } function Clients({ connector }: { connector: string }): JSX.Element { return ( {/* Use With to react to workspace changes, so we know which WS is active */} {(wss: any[]) => { let active_ws_for_monitor = wss.find((ws: any) => ws.is_active && ws.output === connector)?.id; return ( {/* Generate a derived binding for windows, depending on the active workspace */} wins.filter((w: any) => !w.title?.includes("rofi")) .filter((w: any) => w.workspace_id === active_ws_for_monitor) .sort((a: any, b: any) => { const posA = a.layout?.pos_in_scrolling_layout?.[0] ?? 0; const posB = b.layout?.pos_in_scrolling_layout?.[0] ?? 0; return posA - posB; }) )}> {(win: any) => ( )} ); }} ); } export default function Bar(gdkmonitor: Gdk.Monitor, scaleFactor: number = 1) { console.log("Creating Bar on monitor:", gdkmonitor.get_connector()); const connector = gdkmonitor.get_connector() || ""; return (
); }