This commit is contained in:
Nickolaj Jepsen 2025-02-20 22:50:06 +01:00
parent 2b7b63a18c
commit 638ef7093e
140 changed files with 307 additions and 121 deletions

View file

@ -0,0 +1,13 @@
import { Gdk, Gtk } from "astal/gtk4";
export const hasIcon = (name: string): boolean => {
if (!name) {
return false;
}
const display = Gdk.Display.get_default();
if (!display) {
return false;
}
return Gtk.IconTheme.get_for_display(display).has_icon(name);
};

View file

@ -0,0 +1,59 @@
import { GLib, Gio } from "astal";
interface WalkDirOptions {
pattern: string;
}
export const findFiles = async (
path: string | Gio.File,
options?: WalkDirOptions,
): Promise<string[]> => {
const patternSpec = options?.pattern
? GLib.PatternSpec.new(options.pattern)
: null;
// const src = Gio.File.new_for_path(path);
const src = typeof path === "string" ? Gio.File.new_for_path(path) : path;
const root = Gio.File.new_for_path(".");
const iter = src.enumerate_children(
"standard::*",
Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS,
null,
);
const result = [];
while (true) {
const [open, info, file] = iter.iterate(null);
if (!open || !info) {
break; // end of iterator
}
if (!file) {
throw new Error("Failed to get file");
}
const file_type = info.get_file_type();
if (file_type === Gio.FileType.DIRECTORY) {
result.push(...(await findFiles(file, options)));
}
if (info.get_file_type() !== Gio.FileType.REGULAR) {
continue;
}
const name = info.get_name();
const path = root.get_relative_path(file);
if (!path) {
throw new Error("Failed to get relative path");
}
if (patternSpec && !patternSpec.match_string(name)) {
continue;
}
result.push(`./${path}`);
}
return result;
};

View file

@ -0,0 +1,53 @@
import Hyprland from "gi://AstalHyprland";
import { App, type Gdk } from "astal/gtk4";
import config from "../config";
export type SecondaryMonitor = Gdk.Monitor & {
relation: "left" | "right" | "top" | "bottom";
};
export const getMonitors = (): {
main: Gdk.Monitor;
secondary: SecondaryMonitor[];
} => {
const scanFn = [
// Monitor in config
(monitor: Gdk.Monitor) => config.monitor.main === monitor.get_connector(),
// First monitor
() => true,
];
const monitors = App.get_monitors();
console.log("config.monitor.main", config.monitor.main);
const main =
scanFn.map((fn) => monitors.find(fn)).find((m) => m) || monitors[0];
const secondary = monitors
.filter((m) => m !== main)
.map((m) => {
const monitor = m as SecondaryMonitor;
const { x: mx, y: my } = main.get_geometry();
const { x, y } = m.get_geometry();
const verticalDiff = Math.abs(y - my);
const horizontalDiff = Math.abs(x - mx);
if (verticalDiff > horizontalDiff) {
monitor.relation = y < my ? "top" : "bottom";
} else {
monitor.relation = x < mx ? "left" : "right";
}
return monitor;
});
return { main, secondary };
};
export function getHyprlandMonitor(monitor: Gdk.Monitor): Hyprland.Monitor {
const hyprland = Hyprland.get_default();
const monitors = hyprland.get_monitors();
for (const hyprmonitor of monitors) {
if (hyprmonitor.get_name() === monitor.get_connector()) return hyprmonitor;
}
throw new Error("GDK monitor does not map to a Hyprland monitor");
}

View file

@ -0,0 +1,24 @@
import { GLib, type Time, timeout } from "astal";
const runningTimeouts: Map<string, Time> = new Map();
export const cancelTimeout = (id: string) => {
const runningTimeout = runningTimeouts.get(id);
if (runningTimeout) {
runningTimeout.cancel();
runningTimeouts.delete(id);
}
};
export const cancelableTimeout = (
callback: () => void,
id: string,
delay: number,
) => {
cancelTimeout(id);
runningTimeouts.set(
id,
timeout(delay, () => {
callback();
runningTimeouts.delete(id);
}),
);
};

View file

@ -0,0 +1,55 @@
import type { Subscribable } from "astal/binding";
import { Gtk } from "astal/gtk4";
export class VarMap<K, T = Gtk.Widget> implements Subscribable {
#subs = new Set<(v: T[]) => void>();
protected map: Map<K, T>;
#notifiy() {
const value = this.get();
for (const sub of this.#subs) {
sub(value);
}
}
#delete(key: K) {
const v = this.map.get(key);
if (v instanceof Gtk.Widget) {
v.unparent();
}
this.map.delete(key);
}
constructor(initial?: Iterable<[K, T]>) {
this.map = new Map(initial);
}
clear() {
for (const key of this.map.keys()) {
this.#delete(key);
}
this.#notifiy();
}
set(key: K, value: T) {
this.#delete(key);
this.map.set(key, value);
this.#notifiy();
}
delete(key: K) {
this.#delete(key);
this.#notifiy();
}
get() {
return [...this.map.values()];
}
subscribe(callback: (v: T[]) => void) {
this.#subs.add(callback);
return () => this.#subs.delete(callback);
}
}