diff options
| author | 2 * r + 2 * t <61896496+soramanew@users.noreply.github.com> | 2025-04-01 13:36:23 +1100 |
|---|---|---|
| committer | 2 * r + 2 * t <61896496+soramanew@users.noreply.github.com> | 2025-04-01 13:36:23 +1100 |
| commit | d05b124609ca56c99ff9ef32aa2e5217bcde295e (patch) | |
| tree | 1cb164dbf4b9bac9f41296e19c6c666a16487cb3 /src | |
| parent | cleanup: dispose of file monitors when unneeded (diff) | |
| download | caelestia-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.ts | 18 | ||||
| -rw-r--r-- | src/utils/thumbnailer.ts | 69 | ||||
| -rw-r--r-- | src/widgets/notification.tsx | 9 |
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)) |