import { App, Widget } from "astal/gtk3"; import { Bar } from "./window/Bar"; import { OSD } from "./window/OSD"; import { ControlCenter } from "./window/ControlCenter"; import { CenterWindow } from "./window/CenterWindow"; import { LogoutMenu } from "./window/LogoutMenu"; import { FloatingNotifications } from "./window/FloatingNotifications"; import { AppsWindow } from "./window/AppsWindow"; import AstalHyprland from "gi://AstalHyprland"; import { GObject } from "astal"; /** * get open windows / interact with windows(e.g.: close, open or toggle) */ export const Windows = GObject.registerClass({ GTypeName: "Windows", Signals: { "open": { param_types: [ GObject.TYPE_STRING ] }, "close": { param_types: [ GObject.TYPE_STRING ] } }, Properties: { "open-windows": GObject.ParamSpec.jsobject( "open-windows", "Open Windows", "A Readonly object that stores open GTK+ Windows", GObject.ParamFlags.READABLE ) } }, class Windows extends GObject.Object { #openWindows: Record> = {}; static #instance: (Windows | null); #windows: Record (Widget.Window | Array))> = { "bar": this.createWindowForMonitors(Bar), "osd": this.createWindowForFocusedMonitor(OSD), "control-center": this.createWindowForFocusedMonitor(ControlCenter), "center-window": this.createWindowForFocusedMonitor(CenterWindow), "logout-menu": this.createWindowForFocusedMonitor(LogoutMenu), "floating-notifications": this.createWindowForFocusedMonitor(FloatingNotifications), "apps-window": this.createWindowForFocusedMonitor(AppsWindow) }; #windowConnections: Record | Array>)> = {}; #appConnections: Array = []; get windows() { return this.#windows; } get openWindows(): Record> { return this.#openWindows; }; constructor() { super(); // Listen to monitor events this.#appConnections.push( App.connect("monitor-added", () => this.reopen()), App.connect("monitor-removed", () => this.reopen()) ); } vfunc_dispose() { Object.keys(this.#windowConnections).map(name => this.disconnectWindow(name)); this.#appConnections.map(id => GObject.signal_handler_is_connected(App, id) && App.disconnect(id)); } private disconnectWindow(name: keyof typeof this.windows) { const window = this.openWindows[name]; if(!window) { console.log("couldn't disconnect, window is not open"); return; } this.#windowConnections[name].map((id: Array | number) => { if(Array.isArray(window)) { window.map((win, i) => { const curId = (id as Array)[i]; GObject.signal_handler_is_connected(win, curId) && win.disconnect(curId); }); return; } console.log(id as number); GObject.signal_handler_is_connected(window, id as number) && window.disconnect(id as number); }); delete this.#windowConnections[name]; } private connectWindow(name: keyof typeof this.windows) { if(Object.hasOwn(this.#windowConnections, name)) return; if(!this.openWindows?.[name]) { console.log(`${name} is not open, will not connect`); return; } if(Array.isArray(this.openWindows[name])) { this.#windowConnections[name] = this.openWindows[name].map(win => [ win.connect("map", (window) => { if(this.isVisible(name)) return; this.#openWindows[name] = window; this.notify("open-windows"); }), win.connect("destroy", () => { this.disconnectWindow(name); delete this.#openWindows[name]; this.notify("open-windows"); }) ]); return; } this.#windowConnections[name] = [ this.openWindows[name].connect("map", (window) => { if(this.isVisible(name)) return; this.#openWindows[name] = window; this.notify("open-windows"); }), this.openWindows[name].connect("destroy", () => { this.disconnectWindow(name); delete this.#openWindows[name]; this.notify("open-windows"); }) ]; } public static getDefault(): Windows { if(!this.#instance) this.#instance = new Windows(); return this.#instance; } public createWindowForMonitors(windowFun: (mon: number) => Widget.Window): (() => Array) { return () => AstalHyprland.get_default().get_monitors().map(mon => windowFun(mon.id)); } public createWindowForFocusedMonitor(windowFun: (mon: number) => Widget.Window): (() => Widget.Window) { return () => windowFun(AstalHyprland.get_default().get_monitors().filter(mon => mon.focused)[0].id); } public addWindow(name: string, window: (() => (Widget.Window | Array))): void { this.#windows[name] = window; } public hasWindow(name: keyof typeof this.windows): boolean { return Boolean(this.windows?.[name as keyof typeof this.windows]); } public getWindow(name: (keyof typeof this.windows | string)): ((() => (Widget.Window | Array)) | undefined) { return this.windows?.[name as keyof typeof this.windows]; } public getOpenWindow(name: (keyof typeof this.openWindows)): (Widget.Window | Array | undefined) { return this.openWindows?.[name as keyof typeof this.openWindows]; } public getWindows(): Array<(() => (Widget.Window | Array))> { return Object.values(this.windows); } public isVisible(name: keyof typeof this.windows): boolean { return Object.hasOwn(this.#openWindows, name) ?? Object.hasOwn(this.#windowConnections, name); } public open(name: keyof typeof this.windows): void { if(this.isVisible(name)) return; let window: (() => (Widget.Window | Array)) = this.getWindow(name)!; const openWindows: (Array | Widget.Window) = window(); this.#openWindows[name] = openWindows; this.connectWindow(name); this.emit("open", name); this.notify("open-windows"); if(Array.isArray(openWindows)) { openWindows.map(win => win.show()); return; } openWindows.show(); } public close(name: keyof typeof this.windows): void { if(!this.isVisible(name)) return; this.disconnectWindow(name); const windows = this.#openWindows[name]; delete this.#openWindows[name]; if(Array.isArray(windows)) { windows.map(win => win.close()); this.emit("close", name); this.notify("open-windows"); return; } windows.close(); this.emit("close", name); this.notify("open-windows"); } public toggle(name: keyof typeof this.windows): void { this.isVisible(name) ? this.close(name) : this.open(name); } public closeAll(): void { Object.keys(this.openWindows).map(name => this.close(name)); } public reopen(): void { const openWins = Object.keys(this.openWindows); this.closeAll(); openWins.map(name => this.open(name)); } }).getDefault();