summaryrefslogtreecommitdiff
path: root/src/utils
diff options
context:
space:
mode:
Diffstat (limited to 'src/utils')
-rw-r--r--src/utils/icons.ts86
-rw-r--r--src/utils/mpris.ts16
-rw-r--r--src/utils/strings.ts1
-rw-r--r--src/utils/system.ts21
-rw-r--r--src/utils/widgets.ts45
5 files changed, 169 insertions, 0 deletions
diff --git a/src/utils/icons.ts b/src/utils/icons.ts
new file mode 100644
index 0000000..f12aee0
--- /dev/null
+++ b/src/utils/icons.ts
@@ -0,0 +1,86 @@
+import { Gio } from "astal";
+import type AstalApps from "gi://AstalApps";
+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,
+};
+
+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
new file mode 100644
index 0000000..e0cc111
--- /dev/null
+++ b/src/utils/mpris.ts
@@ -0,0 +1,16 @@
+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
new file mode 100644
index 0000000..e5bc43e
--- /dev/null
+++ b/src/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/src/utils/system.ts b/src/utils/system.ts
new file mode 100644
index 0000000..5d77908
--- /dev/null
+++ b/src/utils/system.ts
@@ -0,0 +1,21 @@
+import { execAsync, GLib } from "astal";
+import type AstalApps from "gi://AstalApps";
+import { osIcons } from "./icons";
+
+export const launch = (app: AstalApps.Application) => {
+ execAsync(["uwsm", "app", "--", app.entry]).catch(() => {
+ app.frequency--; // Decrement frequency cause launch also increments it
+ app.launch();
+ });
+ app.frequency++;
+};
+
+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/src/utils/widgets.ts b/src/utils/widgets.ts
new file mode 100644
index 0000000..08f9740
--- /dev/null
+++ b/src/utils/widgets.ts
@@ -0,0 +1,45 @@
+import { Binding } from "astal";
+import { Astal, Widget } from "astal/gtk3";
+import AstalHyprland from "gi://AstalHyprland";
+
+export const setupCustomTooltip = (self: any, text: string | Binding<string>) => {
+ if (!text) return null;
+
+ const window = new Widget.Window({
+ visible: false,
+ namespace: "caelestia-tooltip",
+ keymode: Astal.Keymode.NONE,
+ exclusivity: Astal.Exclusivity.IGNORE,
+ anchor: Astal.WindowAnchor.TOP | Astal.WindowAnchor.LEFT,
+ child: new Widget.Label({ className: "tooltip", label: text }),
+ });
+ 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;
+};
+
+export const setupChildClickthrough = (self: any) =>
+ self.connect("size-allocate", () => self.get_window()?.set_child_input_shapes());