diff options
Diffstat (limited to 'utils')
| -rw-r--r-- | utils/constants.ts | 3 | ||||
| -rw-r--r-- | utils/icons.ts | 118 | ||||
| -rw-r--r-- | utils/mpris.ts | 16 | ||||
| -rw-r--r-- | utils/strings.ts | 1 | ||||
| -rw-r--r-- | utils/system.ts | 21 | ||||
| -rw-r--r-- | utils/widgets.tsx | 45 |
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; +}; |