import Gio from "gi://Gio"; import GLib from "gi://GLib"; import { readFileAsync } from "astal/file"; export async function readFromStreamRaw( stream: Gio.InputStream, bytes: number, ): Promise { return new Promise((resolve, reject) => { stream.read_bytes_async( bytes, GLib.PRIORITY_DEFAULT, null, (stream, result) => { try { const data = stream!.read_bytes_finish(result); const buffer = data.get_data(); if (buffer === null) { reject(new Error("Failed to read from stream")); return; } resolve(buffer); } catch (e) { reject(e); } }, ); }); } export async function readFromStream( stream: Gio.InputStream, bytes: number, ): Promise { const chunkCount = Math.ceil(bytes / 4096); const buffer = await Array.from({ length: chunkCount }, (_, i) => i).reduce( async (acc, i) => { const buffer = await acc; const chunkSize = Math.min(4096, bytes - i * 4096); const chunk = await readFromStreamRaw(stream, chunkSize); buffer.set(chunk, i * 4096); return buffer; }, Promise.resolve(new Uint8Array(bytes)), ); return buffer; } export async function writeToStream( stream: Gio.OutputStream, data: ArrayBuffer | Uint8Array, ): Promise { if (data instanceof ArrayBuffer) { data = new Uint8Array(data); } return new Promise((resolve, reject) => { stream.write_all_async( data as Uint8Array, GLib.PRIORITY_DEFAULT, null, (stream, result) => { try { stream?.write_all_finish(result) ?? reject(new Error("Failed to write to stream")); resolve(); } catch (e) { reject(e); } }, ); }); } export async function dereferenceSymbolicLink( filename: string, ): Promise { return new Promise((resolve, reject) => { console.log(`Dereferencing symbolic link for ${filename}`); const file = Gio.File.new_for_path(filename); file.query_info_async( "standard::is-symlink,standard::symlink-target", Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS, GLib.PRIORITY_DEFAULT, null, (file, result) => { try { const info = file!.query_info_finish(result); if (info.get_is_symlink()) { console.log("File is a symlink"); const target = info.get_symlink_target() as string; console.log(target); if (target.startsWith("./") || target.startsWith("../")) { resolve(file!.resolve_relative_path(target).get_path()!); } else if (target.startsWith("/")) { resolve(target); } else { resolve(file!.get_parent()!.get_child(target).get_path()!); } resolve(file!.resolve_relative_path(target).get_path()!); } else { resolve(filename); } } catch (e) { console.error(e); reject(e); } }, ); }); } const dbusXml = new Map(); export async function getDbusXml(ifaceName: string) { if (dbusXml.has(ifaceName)) { return dbusXml.get(ifaceName) as string; } const contents = await readFileAsync(`dbus/${ifaceName}.xml`); dbusXml.set(ifaceName, contents); return contents; } declare global { // Add new "rotate" method to Array interface Array { /** * Rotates the array by `n` positions. * * @param n The number of positions to rotate the array by. * @returns The rotated array. */ rotate(n: number): T[]; } } Array.prototype.rotate = function (this: T[], n: number): T[] { const array = this; const length = array.length; if (length === 0) { return []; } n = ((n % length) + length) % length; return array.slice(n).concat(array.slice(0, n)); };