summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
author2 * r + 2 * t <61896496+soramanew@users.noreply.github.com>2025-04-01 13:36:23 +1100
committer2 * r + 2 * t <61896496+soramanew@users.noreply.github.com>2025-04-01 13:36:23 +1100
commitd05b124609ca56c99ff9ef32aa2e5217bcde295e (patch)
tree1cb164dbf4b9bac9f41296e19c6c666a16487cb3 /src
parentcleanup: dispose of file monitors when unneeded (diff)
downloadcaelestia-shell-d05b124609ca56c99ff9ef32aa2e5217bcde295e.tar.gz
caelestia-shell-d05b124609ca56c99ff9ef32aa2e5217bcde295e.tar.bz2
caelestia-shell-d05b124609ca56c99ff9ef32aa2e5217bcde295e.zip
feat: thumbnailer utility
Fix large images in notifications being slow GTK css background image is really slow for scaling for some reason, so thumbnail
Diffstat (limited to 'src')
-rw-r--r--src/services/wallpapers.ts18
-rw-r--r--src/utils/thumbnailer.ts69
-rw-r--r--src/widgets/notification.tsx9
3 files changed, 78 insertions, 18 deletions
diff --git a/src/services/wallpapers.ts b/src/services/wallpapers.ts
index 4c7c49b..f0a68af 100644
--- a/src/services/wallpapers.ts
+++ b/src/services/wallpapers.ts
@@ -1,6 +1,6 @@
-import { basename } from "@/utils/strings";
import { monitorDirectory } from "@/utils/system";
-import { execAsync, GLib, GObject, property, register } from "astal";
+import Thumbnailer from "@/utils/thumbnailer";
+import { execAsync, GObject, property, register } from "astal";
import { wallpapers as config } from "config";
export interface IWallpaper {
@@ -22,8 +22,6 @@ export default class Wallpapers extends GObject.Object {
return this.instance;
}
- #thumbnailDir = `${CACHE}/thumbnails`;
-
#list: IWallpaper[] = [];
#categories: ICategory[] = [];
@@ -37,14 +35,6 @@ export default class Wallpapers extends GObject.Object {
return this.#categories;
}
- async #thumbnail(path: string) {
- const dir = path.slice(1, path.lastIndexOf("/")).replaceAll("/", "-");
- const thumbPath = `${this.#thumbnailDir}/${dir}-${basename(path)}.jpg`;
- if (!GLib.file_test(thumbPath, GLib.FileTest.EXISTS))
- await execAsync(`magick -define jpeg:size=1000x500 ${path} -thumbnail 500x250 -unsharp 0x.5 ${thumbPath}`);
- return thumbPath;
- }
-
#listDir(path: { path: string; recursive: boolean }, type: "f" | "d") {
const absPath = path.path.replace("~", HOME);
const maxDepth = path.recursive ? "" : "-maxdepth 1";
@@ -68,7 +58,7 @@ export default class Wallpapers extends GObject.Object {
const files = successes.map(r => r.files.replaceAll("\n", " ")).join(" ");
const list = (await execAsync(["fish", "-c", `identify -ping -format '%i\n' ${files} ; true`])).split("\n");
- this.#list = await Promise.all(list.map(async p => ({ path: p, thumbnail: await this.#thumbnail(p) })));
+ this.#list = await Promise.all(list.map(async p => ({ path: p, thumbnail: await Thumbnailer.thumbnail(p) })));
this.notify("list");
const categories = await Promise.all(successes.map(r => this.#listDir(r.path, "d")));
@@ -82,8 +72,6 @@ export default class Wallpapers extends GObject.Object {
constructor() {
super();
- GLib.mkdir_with_parents(this.#thumbnailDir, 0o755);
-
this.update().catch(console.error);
let monitors = config.paths
diff --git a/src/utils/thumbnailer.ts b/src/utils/thumbnailer.ts
new file mode 100644
index 0000000..a922590
--- /dev/null
+++ b/src/utils/thumbnailer.ts
@@ -0,0 +1,69 @@
+import { execAsync, Gio, GLib } from "astal";
+import { basename } from "./strings";
+
+export default class Thumbnailer {
+ static readonly thumbnailDir = `${CACHE}/thumbnails`;
+
+ static lazy = true;
+ static thumbWidth = 500;
+ static thumbHeight = 250;
+ static maxAttempts = 5;
+ static timeBetweenAttempts = 300;
+
+ static readonly #running = new Set<string>();
+
+ static getThumbPath(path: string) {
+ const dir = path.slice(1, path.lastIndexOf("/")).replaceAll("/", "-");
+ return `${this.thumbnailDir}/${dir}-${basename(path)}.jpg`;
+ }
+
+ static async #thumbnail(path: string, attempts: number): Promise<string> {
+ const thumbPath = this.getThumbPath(path);
+
+ try {
+ await execAsync(
+ `magick -define jpeg:size=${this.thumbWidth * 2}x${this.thumbHeight * 2} ${path} -thumbnail ${
+ this.thumbWidth
+ }x${this.thumbHeight} -unsharp 0x.5 ${thumbPath}`
+ );
+ } catch {
+ if (attempts >= this.maxAttempts) {
+ console.error(`Failed to generate thumbnail for ${path}`);
+ return path;
+ }
+
+ await new Promise(r => setTimeout(r, this.timeBetweenAttempts));
+ return this.#thumbnail(path, attempts + 1);
+ }
+
+ return thumbPath;
+ }
+
+ static async thumbnail(path: string): Promise<string> {
+ let thumbPath = this.getThumbPath(path);
+
+ // If not lazy (i.e. force gen), delete existing thumbnail
+ if (!this.lazy) Gio.File.new_for_path(thumbPath).delete(null);
+
+ // 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, 0);
+
+ this.#running.delete(path);
+ }
+
+ return thumbPath;
+ }
+
+ // Static class
+ private constructor() {}
+
+ static {
+ GLib.mkdir_with_parents(this.thumbnailDir, 0o755);
+ }
+}
diff --git a/src/widgets/notification.tsx b/src/widgets/notification.tsx
index b2a10be..0dfd368 100644
--- a/src/widgets/notification.tsx
+++ b/src/widgets/notification.tsx
@@ -1,4 +1,5 @@
import { desktopEntrySubs } from "@/utils/icons";
+import Thumbnailer from "@/utils/thumbnailer";
import { setupCustomTooltip } from "@/utils/widgets";
import { bind, GLib, register, timeout, Variable } from "astal";
import { Astal, Gtk, Widget } from "astal/gtk3";
@@ -58,9 +59,11 @@ const Image = ({ compact, icon }: { compact?: boolean; icon: string }) => {
<box
valign={Gtk.Align.START}
className={`image ${compact ? "small" : ""}`}
- css={`
- background-image: url("${icon}");
- `}
+ setup={self =>
+ Thumbnailer.thumbnail(icon)
+ .then(p => (self.css = `background-image: url("${p}");`))
+ .catch(console.error)
+ }
/>
);
if (Astal.Icon.lookup_icon(icon))