summaryrefslogtreecommitdiff
path: root/utils
diff options
context:
space:
mode:
Diffstat (limited to 'utils')
-rw-r--r--utils/constants.ts3
-rw-r--r--utils/icons.ts118
-rw-r--r--utils/mpris.ts16
-rw-r--r--utils/strings.ts1
-rw-r--r--utils/system.ts21
-rw-r--r--utils/widgets.tsx45
6 files changed, 204 insertions, 0 deletions
diff --git a/utils/constants.ts b/utils/constants.ts
new file mode 100644
index 0000000..d907014
--- /dev/null
+++ b/utils/constants.ts
@@ -0,0 +1,3 @@
+import { GLib } from "astal";
+
+export const CACHE_DIR = GLib.get_user_cache_dir() + "/caelestia";
diff --git a/utils/icons.ts b/utils/icons.ts
new file mode 100644
index 0000000..0293611
--- /dev/null
+++ b/utils/icons.ts
@@ -0,0 +1,118 @@
+import { Gio } from "astal";
+import { Astal } from "astal/gtk3";
+import { Apps } from "../services/apps";
+
+// Code points from https://www.github.com/lukas-w/font-logos
+export const osIcons: Record<string, number> = {
+ almalinux: 0xf31d,
+ alpine: 0xf300,
+ arch: 0xf303,
+ arcolinux: 0xf346,
+ centos: 0x304,
+ debian: 0xf306,
+ elementary: 0xf309,
+ endeavouros: 0xf322,
+ fedora: 0xf30a,
+ gentoo: 0xf30d,
+ kali: 0xf327,
+ linuxmint: 0xf30e,
+ mageia: 0xf310,
+ manjaro: 0xf312,
+ nixos: 0xf313,
+ opensuse: 0xf314,
+ suse: 0xf314,
+ sles: 0xf314,
+ sles_sap: 0xf314,
+ pop: 0xf32a,
+ raspbian: 0xf315,
+ rhel: 0xf316,
+ rocky: 0xf32b,
+ slackware: 0xf318,
+ ubuntu: 0xf31b,
+};
+
+const appIcons: Record<string, string> = {
+ "code-url-handler": "visual-studio-code",
+ code: "visual-studio-code",
+ "codium-url-handler": "vscodium",
+ codium: "vscodium",
+ "GitHub Desktop": "github-desktop",
+ "gnome-tweaks": "org.gnome.tweaks",
+ "org.pulseaudio.pavucontrol": "pavucontrol",
+ "pavucontrol-qt": "pavucontrol",
+ "jetbrains-pycharm-ce": "pycharm-community",
+ "Spotify Free": "Spotify",
+ safeeyes: "io.github.slgobinath.SafeEyes",
+ "yad-icon-browser": "yad",
+ xterm: "uxterm",
+ "com-atlauncher-App": "atlauncher",
+ avidemux3_qt5: "avidemux",
+};
+
+const appRegex = [
+ { regex: /^steam_app_(\d+)$/, replace: "steam_icon_$1" },
+ { regex: /^Minecraft\* [0-9\.]+$/, replace: "minecraft" },
+];
+
+export const getAppIcon = (name: string) => {
+ if (appIcons.hasOwnProperty(name)) return appIcons[name];
+ for (const { regex, replace } of appRegex) {
+ const postSub = name.replace(regex, replace);
+ if (postSub !== name) return postSub;
+ }
+
+ if (Astal.Icon.lookup_icon(name)) return name;
+
+ const apps = Apps.fuzzy_query(name);
+ if (apps.length > 0) return apps[0].iconName;
+
+ return "image";
+};
+
+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 = (name: string) => {
+ const categories =
+ Gio.DesktopAppInfo.new(`${name}.desktop`)?.get_categories()?.split(";") ??
+ Apps.fuzzy_query(name)[0]?.categories;
+ if (categories)
+ for (const [key, value] of Object.entries(categoryIcons)) if (categories.includes(key)) return value;
+ return "terminal";
+};
diff --git a/utils/mpris.ts b/utils/mpris.ts
new file mode 100644
index 0000000..8f6923a
--- /dev/null
+++ b/utils/mpris.ts
@@ -0,0 +1,16 @@
+import AstalMpris from "gi://AstalMpris";
+import { inPath } from "./system";
+
+const hasPlasmaIntegration = inPath("plasma-browser-integration-host");
+
+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/utils/strings.ts b/utils/strings.ts
new file mode 100644
index 0000000..e5bc43e
--- /dev/null
+++ b/utils/strings.ts
@@ -0,0 +1 @@
+export const ellipsize = (str: string, len = 40) => (str.length > len ? `${str.slice(0, len - 1)}…` : str);
diff --git a/utils/system.ts b/utils/system.ts
new file mode 100644
index 0000000..9a328d5
--- /dev/null
+++ b/utils/system.ts
@@ -0,0 +1,21 @@
+import { exec, GLib } from "astal";
+import { osIcons } from "./icons";
+
+export const inPath = (bin: string) => {
+ try {
+ exec(`which ${bin}`);
+ } catch {
+ return false;
+ }
+ return true;
+};
+
+export const osId = GLib.get_os_info("ID") ?? "unknown";
+export const osIdLike = GLib.get_os_info("ID_LIKE");
+export const osIcon = String.fromCodePoint(
+ (() => {
+ if (osIcons.hasOwnProperty(osId)) return osIcons[osId];
+ if (osIdLike) for (const id of osIdLike.split(" ")) if (osIcons.hasOwnProperty(id)) return osIcons[id];
+ return 0xf31a;
+ })()
+);
diff --git a/utils/widgets.tsx b/utils/widgets.tsx
new file mode 100644
index 0000000..2078aad
--- /dev/null
+++ b/utils/widgets.tsx
@@ -0,0 +1,45 @@
+import { Binding } from "astal";
+import { Astal, type Widget } from "astal/gtk3";
+import AstalHyprland from "gi://AstalHyprland";
+
+export const setupCustomTooltip = (self: any, text: string | Binding<string>) => {
+ if (!text) return null;
+
+ const window = (
+ <window
+ visible={false}
+ namespace="tooltip"
+ keymode={Astal.Keymode.NONE}
+ exclusivity={Astal.Exclusivity.IGNORE}
+ anchor={Astal.WindowAnchor.TOP | Astal.WindowAnchor.LEFT}
+ >
+ <label className="tooltip" label={text} />
+ </window>
+ ) as Widget.Window;
+ self.set_tooltip_window(window);
+
+ let dirty = true;
+ let lastX = 0;
+ self.connect("size-allocate", () => (dirty = true));
+ window.connect("size-allocate", () => {
+ window.marginLeft = lastX + (self.get_allocated_width() - window.get_preferred_width()[1]) / 2;
+ });
+ if (text instanceof Binding) self.hook(text, (_: any, v: string) => !v && window.hide());
+
+ self.connect("query-tooltip", (_: any, x: number, y: number) => {
+ if (text instanceof Binding && !text.get()) return false;
+ if (dirty) {
+ const { width, height } = self.get_allocation();
+ const { x: cx, y: cy } = AstalHyprland.get_default().get_cursor_position();
+ window.marginLeft = cx + ((width - window.get_preferred_width()[1]) / 2 - x);
+ window.marginTop = cy + (height - y);
+ lastX = cx - x;
+ dirty = false;
+ }
+ return true;
+ });
+
+ self.connect("destroy", () => window.destroy());
+
+ return window;
+};