summaryrefslogtreecommitdiff
path: root/src/utils
diff options
context:
space:
mode:
Diffstat (limited to 'src/utils')
-rw-r--r--src/utils/icons.ts158
-rw-r--r--src/utils/mpris.ts16
-rw-r--r--src/utils/strings.ts18
-rw-r--r--src/utils/system.ts111
-rw-r--r--src/utils/thumbnailer.ts80
-rw-r--r--src/utils/types.ts35
-rw-r--r--src/utils/widgets.ts82
7 files changed, 0 insertions, 500 deletions
diff --git a/src/utils/icons.ts b/src/utils/icons.ts
deleted file mode 100644
index f164692..0000000
--- a/src/utils/icons.ts
+++ /dev/null
@@ -1,158 +0,0 @@
-import { Apps } from "@/services/apps";
-import { Gio } from "astal";
-import type AstalApps from "gi://AstalApps";
-
-export const osIcons: Record<string, string> = {
- almalinux: "",
- alpine: "",
- arch: "",
- archcraft: "",
- arcolinux: "",
- artix: "",
- centos: "",
- debian: "",
- devuan: "",
- elementary: "",
- endeavouros: "",
- fedora: "",
- freebsd: "",
- garuda: "",
- gentoo: "",
- hyperbola: "",
- kali: "",
- linuxmint: "󰣭",
- mageia: "",
- openmandriva: "",
- manjaro: "",
- neon: "",
- nixos: "",
- opensuse: "",
- suse: "",
- sles: "",
- sles_sap: "",
- "opensuse-tumbleweed": "",
- parrot: "",
- pop: "",
- raspbian: "",
- rhel: "",
- rocky: "",
- slackware: "",
- solus: "",
- steamos: "",
- tails: "",
- trisquel: "",
- ubuntu: "",
- vanilla: "",
- void: "",
- zorin: "",
-};
-
-export const weatherIcons: Record<string, string> = {
- warning: "󰼯",
- sunny: "󰖙",
- clear: "󰖔",
- partly_cloudy: "󰖕",
- partly_cloudy_night: "󰼱",
- cloudy: "󰖐",
- overcast: "󰖕",
- mist: "󰖑",
- patchy_rain_nearby: "󰼳",
- patchy_rain_possible: "󰼳",
- patchy_snow_possible: "󰼴",
- patchy_sleet_possible: "󰙿",
- patchy_freezing_drizzle_possible: "󰙿",
- thundery_outbreaks_possible: "󰙾",
- blowing_snow: "󰼶",
- blizzard: "󰼶",
- fog: "󰖑",
- freezing_fog: "󰖑",
- patchy_light_drizzle: "󰼳",
- light_drizzle: "󰼳",
- freezing_drizzle: "󰙿",
- heavy_freezing_drizzle: "󰙿",
- patchy_light_rain: "󰼳",
- light_rain: "󰼳",
- moderate_rain_at_times: "󰖗",
- moderate_rain: "󰼳",
- heavy_rain_at_times: "󰖖",
- heavy_rain: "󰖖",
- light_freezing_rain: "󰙿",
- moderate_or_heavy_freezing_rain: "󰙿",
- light_sleet: "󰙿",
- moderate_or_heavy_sleet: "󰙿",
- patchy_light_snow: "󰼴",
- light_snow: "󰼴",
- patchy_moderate_snow: "󰼴",
- moderate_snow: "󰼶",
- patchy_heavy_snow: "󰼶",
- heavy_snow: "󰼶",
- ice_pellets: "󰖒",
- light_rain_shower: "󰖖",
- moderate_or_heavy_rain_shower: "󰖖",
- torrential_rain_shower: "󰖖",
- light_sleet_showers: "󰼵",
- moderate_or_heavy_sleet_showers: "󰼵",
- light_snow_showers: "󰼵",
- moderate_or_heavy_snow_showers: "󰼵",
- light_showers_of_ice_pellets: "󰖒",
- moderate_or_heavy_showers_of_ice_pellets: "󰖒",
- patchy_light_rain_with_thunder: "󰙾",
- moderate_or_heavy_rain_with_thunder: "󰙾",
- moderate_or_heavy_rain_in_area_with_thunder: "󰙾",
- patchy_light_snow_with_thunder: "󰼶",
- moderate_or_heavy_snow_with_thunder: "󰼶",
-};
-
-export const desktopEntrySubs: Record<string, string> = {
- Firefox: "firefox",
-};
-
-const categoryIcons: Record<string, string> = {
- WebBrowser: "web",
- Printing: "print",
- Security: "security",
- Network: "chat",
- Archiving: "archive",
- Compression: "archive",
- Development: "code",
- IDE: "code",
- TextEditor: "edit_note",
- Audio: "music_note",
- Music: "music_note",
- Player: "music_note",
- Recorder: "mic",
- Game: "sports_esports",
- FileTools: "files",
- FileManager: "files",
- Filesystem: "files",
- FileTransfer: "files",
- Settings: "settings",
- DesktopSettings: "settings",
- HardwareSettings: "settings",
- TerminalEmulator: "terminal",
- ConsoleOnly: "terminal",
- Utility: "build",
- Monitor: "monitor_heart",
- Midi: "graphic_eq",
- Mixer: "graphic_eq",
- AudioVideoEditing: "video_settings",
- AudioVideo: "music_video",
- Video: "videocam",
- Building: "construction",
- Graphics: "photo_library",
- "2DGraphics": "photo_library",
- RasterGraphics: "photo_library",
- TV: "tv",
- System: "host",
-};
-
-export const getAppCategoryIcon = (nameOrApp: string | AstalApps.Application) => {
- const categories =
- typeof nameOrApp === "string"
- ? Gio.DesktopAppInfo.new(`${nameOrApp}.desktop`)?.get_categories()?.split(";") ??
- Apps.fuzzy_query(nameOrApp)[0]?.categories
- : nameOrApp.categories;
- if (categories)
- for (const [key, value] of Object.entries(categoryIcons)) if (categories.includes(key)) return value;
- return "terminal";
-};
diff --git a/src/utils/mpris.ts b/src/utils/mpris.ts
deleted file mode 100644
index e0cc111..0000000
--- a/src/utils/mpris.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import { GLib } from "astal";
-import AstalMpris from "gi://AstalMpris";
-
-const hasPlasmaIntegration = GLib.find_program_in_path("plasma-browser-integration-host") !== null;
-
-export const isRealPlayer = (player?: AstalMpris.Player) =>
- player !== undefined &&
- // Player closed
- player.identity !== null &&
- // Remove unecessary native buses from browsers if there's plasma integration
- !(hasPlasmaIntegration && player.busName.startsWith("org.mpris.MediaPlayer2.firefox")) &&
- !(hasPlasmaIntegration && player.busName.startsWith("org.mpris.MediaPlayer2.chromium")) &&
- // playerctld just copies other buses and we don't need duplicates
- !player.busName.startsWith("org.mpris.MediaPlayer2.playerctld") &&
- // Non-instance mpd bus
- !(player.busName.endsWith(".mpd") && !player.busName.endsWith("MediaPlayer2.mpd"));
diff --git a/src/utils/strings.ts b/src/utils/strings.ts
deleted file mode 100644
index 1edad67..0000000
--- a/src/utils/strings.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-export const basename = (path: string, stripExt = true) => {
- const lastSlash = path.lastIndexOf("/");
- const lastDot = path.lastIndexOf(".");
- return path.slice(lastSlash + 1, stripExt && lastDot > lastSlash ? lastDot : undefined);
-};
-
-export const pathToFileName = (path: string, ext?: string) => {
- const start = /[a-z]+:\/\//.test(path) ? 0 : path.indexOf("/") + 1;
- const dir = path.slice(start, path.lastIndexOf("/")).replaceAll("/", "-");
- return `${dir}-${basename(path, ext !== undefined)}${ext ? `.${ext}` : ""}`;
-};
-
-export const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);
-
-export const lengthStr = (length: number) =>
- `${Math.floor(length / 60)}:${Math.floor(length % 60)
- .toString()
- .padStart(2, "0")}`;
diff --git a/src/utils/system.ts b/src/utils/system.ts
deleted file mode 100644
index 3a9caa6..0000000
--- a/src/utils/system.ts
+++ /dev/null
@@ -1,111 +0,0 @@
-import { bind, execAsync, Gio, GLib, Variable, type Binding } from "astal";
-import type AstalApps from "gi://AstalApps";
-import { osIcons } from "./icons";
-
-/**
- * See https://specifications.freedesktop.org/desktop-entry-spec/latest/exec-variables.html
- * @param exec The exec field in a desktop file
- */
-const execToCmd = (app: AstalApps.Application) => {
- let exec = app.executable.replace(/%[fFuUdDnNvm]/g, ""); // Remove useless field codes
- exec = exec.replace(/%i/g, app.iconName ? `--icon ${app.iconName}` : ""); // Replace %i app icon
- exec = exec.replace(/%c/g, app.name); // Replace %c with app name
- exec = exec.replace(/%k/g, (app.app as Gio.DesktopAppInfo).get_filename() ?? ""); // Replace %k with desktop file path
- return exec;
-};
-
-export const launch = (app: AstalApps.Application) => {
- let now = Date.now();
- execAsync(["app2unit", "--", app.entry]).catch(() => {
- // Try manual exec if launch fails (exits with error within 1 second)
- if (Date.now() - now < 1000) {
- now = Date.now();
- execAsync(["app2unit", "--", execToCmd(app)]).catch(() => {
- // Fallback to regular launch
- if (Date.now() - now < 1000) {
- app.frequency--; // Decrement frequency cause launch also increments it
- app.launch();
- }
- });
- }
- });
- app.frequency++;
-};
-
-export const notify = (props: {
- summary: string;
- body?: string;
- icon?: string;
- urgency?: "low" | "normal" | "critical";
- transient?: boolean;
- actions?: Record<string, () => void>;
-}) =>
- execAsync([
- "notify-send",
- "-a",
- "caelestia-shell",
- ...(props.icon ? ["-i", props.icon] : []),
- ...(props.urgency ? ["-u", props.urgency] : []),
- ...(props.transient ? ["-e"] : []),
- ...Object.keys(props.actions ?? {}).flatMap((k, i) => ["-A", `${i}=${k}`]),
- props.summary,
- ...(props.body ? [props.body] : []),
- ])
- .then(action => props.actions && Object.values(props.actions)[parseInt(action, 10)]?.())
- .catch(console.error);
-
-export const osId = GLib.get_os_info("ID") ?? "unknown";
-export const osIdLike = GLib.get_os_info("ID_LIKE");
-export const osIcon = (() => {
- if (osIcons.hasOwnProperty(osId)) return osIcons[osId];
- if (osIdLike) for (const id of osIdLike.split(" ")) if (osIcons.hasOwnProperty(id)) return osIcons[id];
- return "";
-})();
-
-export const currentTime = Variable(GLib.DateTime.new_now_local()).poll(1000, () => GLib.DateTime.new_now_local());
-export const bindCurrentTime = (
- format: Binding<string> | string,
- fallback?: (time: GLib.DateTime) => string,
- self?: JSX.Element
-) => {
- const fmt = (c: GLib.DateTime, format: string) => c.format(format) ?? fallback?.(c) ?? new Date().toLocaleString();
- if (typeof format === "string") return bind(currentTime).as(c => fmt(c, format));
- if (!self) throw new Error("bindCurrentTime: self is required when format is a Binding");
- const time = Variable.derive([currentTime, format], (c, f) => fmt(c, f));
- self?.connect("destroy", () => time.drop());
- return bind(time);
-};
-
-const monitors = new Set();
-export const monitorDirectory = (
- path: string,
- callback: (
- source: Gio.FileMonitor,
- file: Gio.File,
- other_file: Gio.File | null,
- type: Gio.FileMonitorEvent
- ) => void,
- recursive: boolean = true
-) => {
- const file = Gio.file_new_for_path(path.replace("~", HOME));
- const monitor = file.monitor_directory(null, null);
- monitor.connect("changed", (m, f1, f2, t) => {
- if (t === Gio.FileMonitorEvent.CHANGES_DONE_HINT || t === Gio.FileMonitorEvent.DELETED) callback(m, f1, f2, t);
- });
-
- if (recursive) {
- const enumerator = file.enumerate_children("standard::*", null, null);
- let child;
- while ((child = enumerator.next_file(null)))
- if (child.get_file_type() === Gio.FileType.DIRECTORY) {
- const m = monitorDirectory(`${path}/${child.get_name()}`, callback, recursive);
- monitor.connect("notify::cancelled", () => m.cancel());
- }
- }
-
- // Keep ref to monitor so it doesn't get GC'd
- monitors.add(monitor);
- monitor.connect("notify::cancelled", () => monitor.cancelled && monitors.delete(monitor));
-
- return monitor;
-};
diff --git a/src/utils/thumbnailer.ts b/src/utils/thumbnailer.ts
deleted file mode 100644
index d23dab1..0000000
--- a/src/utils/thumbnailer.ts
+++ /dev/null
@@ -1,80 +0,0 @@
-import { execAsync, GLib, type Variable } from "astal";
-import { thumbnailer as config } from "config";
-
-export interface ThumbOpts {
- width?: number;
- height?: number;
- exact?: boolean;
-}
-
-export default class Thumbnailer {
- static readonly thumbnailDir = `${CACHE}/thumbnails`;
-
- static readonly #running = new Set<string>();
-
- static getOpt<T extends keyof ThumbOpts>(opt: T, opts: ThumbOpts) {
- return opts[opt] ?? (config.defaults[opt] as Variable<NonNullable<ThumbOpts[T]>>).get();
- }
-
- static async getThumbPath(path: string, opts: ThumbOpts) {
- const hash = (await execAsync(`sha1sum ${path}`)).split(" ")[0];
- const size = `${this.getOpt("width", opts)}x${this.getOpt("height", opts)}`;
- const exact = this.getOpt("exact", opts) ? "-exact" : "";
- return `${this.thumbnailDir}/${hash}@${size}${exact}.png`;
- }
-
- static async shouldThumbnail(path: string, opts: ThumbOpts) {
- const [w, h] = (await execAsync(`identify -ping -format "%w %h" ${path}`)).split(" ").map(parseInt);
- return w > this.getOpt("width", opts) || h > this.getOpt("height", opts);
- }
-
- static async #thumbnail(path: string, opts: ThumbOpts, attempts: number): Promise<string> {
- const thumbPath = await this.getThumbPath(path, opts);
-
- try {
- const width = this.getOpt("width", opts);
- const height = this.getOpt("height", opts);
- const cropCmd = this.getOpt("exact", opts)
- ? `-background none -gravity center -extent ${width}x${height}`
- : "";
- await execAsync(`magick ${path} -thumbnail ${width}x${height}^ ${cropCmd} -unsharp 0x.5 ${thumbPath}`);
- } catch {
- if (attempts >= config.maxAttempts.get()) {
- console.error(`Failed to generate thumbnail for ${path}`);
- return path;
- }
-
- await new Promise(r => setTimeout(r, config.timeBetweenAttempts.get()));
- return this.#thumbnail(path, opts, attempts + 1);
- }
-
- return thumbPath;
- }
-
- static async thumbnail(path: string, opts: ThumbOpts = {}): Promise<string> {
- if (!(await this.shouldThumbnail(path, opts))) return path;
-
- let thumbPath = await this.getThumbPath(path, opts);
-
- // Wait for existing thumbnail for path to finish
- while (this.#running.has(path)) await new Promise(r => setTimeout(r, 100));
-
- // If no thumbnail, generate
- if (!GLib.file_test(thumbPath, GLib.FileTest.EXISTS)) {
- this.#running.add(path);
-
- thumbPath = await this.#thumbnail(path, opts, 0);
-
- this.#running.delete(path);
- }
-
- return thumbPath;
- }
-
- // Static class
- private constructor() {}
-
- static {
- GLib.mkdir_with_parents(this.thumbnailDir, 0o755);
- }
-}
diff --git a/src/utils/types.ts b/src/utils/types.ts
deleted file mode 100644
index d2c1943..0000000
--- a/src/utils/types.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-import type { astalify } from "astal/gtk3";
-import type AstalHyprland from "gi://AstalHyprland";
-
-export type AstalWidget = InstanceType<ReturnType<typeof astalify>>;
-
-export type Address = `0x${string}`;
-
-export interface Client {
- address: Address;
- mapped: boolean;
- hidden: boolean;
- at: [number, number];
- size: [number, number];
- workspace: {
- id: number;
- name: string;
- };
- floating: boolean;
- pseudo: boolean;
- monitor: number;
- class: string;
- title: string;
- initialClass: string;
- initialTitle: string;
- pid: number;
- xwayland: boolean;
- pinned: boolean;
- fullscreen: AstalHyprland.Fullscreen;
- fullscreenClient: AstalHyprland.Fullscreen;
- grouped: Address[];
- tags: string[];
- swallowing: string;
- focusHistoryID: number;
- inhibitingIdle: boolean;
-}
diff --git a/src/utils/widgets.ts b/src/utils/widgets.ts
deleted file mode 100644
index bef79f2..0000000
--- a/src/utils/widgets.ts
+++ /dev/null
@@ -1,82 +0,0 @@
-import { Binding, idle, register } from "astal";
-import { Astal, astalify, Gtk, Widget, type ConstructProps } from "astal/gtk3";
-import AstalHyprland from "gi://AstalHyprland";
-import type { AstalWidget } from "./types";
-
-export const setupCustomTooltip = (
- self: AstalWidget,
- text: string | Binding<string>,
- labelProps: Widget.LabelProps = {}
-) => {
- if (!text) return null;
-
- self.set_has_tooltip(true);
-
- const window = new Widget.Window({
- visible: false,
- namespace: "caelestia-tooltip",
- layer: Astal.Layer.OVERLAY,
- keymode: Astal.Keymode.NONE,
- exclusivity: Astal.Exclusivity.IGNORE,
- anchor: Astal.WindowAnchor.TOP | Astal.WindowAnchor.LEFT,
- child: new Widget.Label({ ...labelProps, className: "tooltip", label: text }),
- });
- self.set_tooltip_window(window);
-
- if (text instanceof Binding) self.hook(text, (_, v) => !v && window.hide());
-
- const positionWindow = ({ x, y }: { x: number; y: number }) => {
- const { width: mWidth, height: mHeight } = AstalHyprland.get_default().get_focused_monitor();
- const { width: pWidth, height: pHeight } = window.get_preferred_size()[1]!;
- const cursorSize = Gtk.Settings.get_default()?.gtkCursorThemeSize ?? 0;
-
- let marginLeft = x - pWidth / 2;
- if (marginLeft < 0) marginLeft = 0;
- else if (marginLeft + pWidth > mWidth) marginLeft = mWidth - pWidth;
-
- let marginTop = y + cursorSize;
- if (marginTop < 0) marginTop = 0;
- else if (marginTop + pHeight > mHeight) marginTop = y - pHeight;
-
- window.marginLeft = marginLeft;
- window.marginTop = marginTop;
- };
-
- let lastPos = { x: 0, y: 0 };
-
- window.connect("size-allocate", () => positionWindow(lastPos));
- self.connect("query-tooltip", () => {
- if (text instanceof Binding && !text.get()) return false;
- if (window.visible) return true;
-
- const cPos = AstalHyprland.get_default().get_cursor_position();
- positionWindow(cPos);
- lastPos = cPos;
-
- return true;
- });
-
- self.connect("destroy", () => window.destroy());
-
- return window;
-};
-
-export const setupChildClickthrough = (self: AstalWidget) => {
- self.connect("size-allocate", () => self.get_window()?.set_child_input_shapes());
- self.set_size_request(1, 1);
- idle(() => self.set_size_request(-1, -1));
-};
-
-@register()
-export class MenuItem extends astalify(Gtk.MenuItem) {
- constructor(props: ConstructProps<MenuItem, Gtk.MenuItem.ConstructorProps, { onActivate: [] }>) {
- super(props as any);
- }
-}
-
-@register()
-export class FlowBox extends astalify(Gtk.FlowBox) {
- constructor(props: ConstructProps<FlowBox, Gtk.FlowBox.ConstructorProps>) {
- super(props as any);
- }
-}