import AstalTray from "gi://AstalTray"; import { bind, GObject } from "astal"; import AstalHyprland from "gi://AstalHyprland"; import { Gtk } from "astal/gtk4"; const SYNC = GObject.BindingFlags.SYNC_CREATE; const SysTray = () => { const trayBox = new Gtk.Box({ cssClasses: ["bar-button"] }); const tray = AstalTray.get_default(); const trayItems = new Map(); const trayAddedHandler = tray.connect("item-added", (_, id) => { const item = tray.get_item(id); const popover = Gtk.PopoverMenu.new_from_model(item.menu_model); const icon = new Gtk.Image(); const button = new Gtk.MenuButton({ popover, child: icon, cssClasses: ["tray-item"], }); item.bind_property("gicon", icon, "gicon", SYNC); popover.insert_action_group("dbusmenu", item.action_group); item.connect("notify::action-group", () => { popover.insert_action_group("dbusmenu", item.action_group); }); trayItems.set(id, button); trayBox.append(button); }); const trayRemovedHandler = tray.connect("item-removed", (_, id) => { const button = trayItems.get(id); if (button) { trayBox.remove(button); button.run_dispose(); trayItems.delete(id); } }); trayBox.connect("destroy", () => { tray.disconnect(trayAddedHandler); tray.disconnect(trayRemovedHandler); }); return trayBox; }; const Workspace = () => { const hypr = AstalHyprland.get_default(); return ( {bind(hypr, "workspaces").as(wss => wss .filter(ws => !(ws.id >= -99 && ws.id <= -2)) // filter out special workspaces .sort((a, b) => a.id - b.id) .map(ws => ( )), )} ); }; /** * Displays the name of the currently active window and provides a popover for * displaying all available clients */ const ActiveWindow = () => { const hypr = AstalHyprland.get_default(); const focused = bind(hypr, "focusedClient"); const WindowPopover = (): Gtk.Popover => { // Set up boxes + Popover const clients = new Map(); const popover = new Gtk.Popover(); const popoverBox = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL, }); const widgetTitle = new Gtk.Label({ cssClasses: ["title-2"], label: "Available Windows", }); popoverBox.append(widgetTitle); const seaparator = new Gtk.Separator({ marginTop: 5, marginBottom: 10, }); popoverBox.append(seaparator); const addClient = (client: AstalHyprland.Client) => { const clientBox = new Gtk.Box(); // Workspace description const descWS = new Gtk.Label({ label: "(WS " }); // Workpsace information const workspace = new Gtk.Label(); client.workspace.bind_property("name", workspace, "label", SYNC); const windowClassDesc = new Gtk.Label({ label: ") [" }); const windowClass = new Gtk.Label(); windowClass.label = client.get_initial_class(); const titleDesc = new Gtk.Label({ label: "] " }); titleDesc.set_margin_end(2); const title = new Gtk.Label(); client.bind_property("title", title, "label", SYNC); clientBox.append(descWS); clientBox.append(workspace); clientBox.append(windowClassDesc); clientBox.append(windowClass); clientBox.append(titleDesc); clientBox.append(title); const button = new Gtk.Button(); button.connect( 'clicked', () => { client.workspace.focus(); } ); button.set_child(clientBox); popoverBox.append(button); clients.set(client.get_address(), button); }; // Populate with already added clients const c = hypr.get_clients(); for (let index = 0; index < c.length; index++) { addClient(c[index]); } hypr.connect("client-added", (_, client) => { addClient(client); }); hypr.connect("client-removed", (_, client) => { const c = clients.get(client); if (c) { popoverBox.remove(c); c.run_dispose(); clients.delete(client); } }); popover.set_child(popoverBox); return popover; }; const windowPopover = WindowPopover(); // ─────────────────────────────────────────────────────────────────── // Return fully assembled HyprlandFocusedClient box // ─────────────────────────────────────────────────────────────────── return ( {windowPopover} ); }; export default { Workspace, ActiveWindow, SysTray, };