summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/modules/launcher/actions.tsx28
-rw-r--r--src/services/schemes.ts8
-rw-r--r--src/services/wallpapers.ts81
-rw-r--r--src/utils/strings.ts2
4 files changed, 113 insertions, 6 deletions
diff --git a/src/modules/launcher/actions.tsx b/src/modules/launcher/actions.tsx
index 75f1092..a8c5d0a 100644
--- a/src/modules/launcher/actions.tsx
+++ b/src/modules/launcher/actions.tsx
@@ -1,6 +1,8 @@
import { Apps } from "@/services/apps";
import type { IPalette } from "@/services/palette";
import Schemes from "@/services/schemes";
+import Wallpapers from "@/services/wallpapers";
+import { basename } from "@/utils/strings";
import { notify } from "@/utils/system";
import { setupCustomTooltip, type FlowBox } from "@/utils/widgets";
import { execAsync, GLib, readFile, register, type Variable } from "astal";
@@ -218,6 +220,28 @@ const Scheme = ({ name, colours }: { name: string; colours: IPalette }) => (
</Gtk.FlowBoxChild>
);
+const Wallpaper = ({ path, thumbnail }: { path: string; thumbnail?: string }) => (
+ <Gtk.FlowBoxChild visible canFocus={false}>
+ <button
+ className="result"
+ cursor="pointer"
+ onClicked={() => {
+ execAsync(`caelestia wallpaper -f ${path}`).catch(console.error);
+ close();
+ }}
+ setup={self => setupCustomTooltip(self, path.replace(HOME, "~"))}
+ >
+ <box
+ vertical={config.wallpaper.style.get() !== "compact"}
+ className={`wallpaper ${config.wallpaper.style.get()}`}
+ >
+ <box className="thumbnail" css={"background-image: url('" + (thumbnail ?? path) + "');"} />
+ <label truncate label={basename(path)} />
+ </box>
+ </button>
+ </Gtk.FlowBoxChild>
+);
+
@register()
export default class Actions extends Widget.Box implements LauncherContent {
#map: ActionMap;
@@ -249,6 +273,10 @@ export default class Actions extends Widget.Box implements LauncherContent {
const scheme = args[1] ?? "";
for (const { target } of fuzzysort.go(scheme, Object.keys(Schemes.get_default().map), { all: true }))
this.#content.add(<Scheme name={target} colours={Schemes.get_default().map[target]} />);
+ } else if (action === "wallpaper") {
+ const wallpaper = args[1] ?? "";
+ for (const { obj } of fuzzysort.go(wallpaper, Wallpapers.get_default().list, { all: true, key: "path" }))
+ this.#content.add(<Wallpaper {...obj} />);
} else {
for (const { target } of fuzzysort.go(action, this.#list, { all: true }))
this.#content.add(<Action {...this.#map[target]} args={args.slice(1)} />);
diff --git a/src/services/schemes.ts b/src/services/schemes.ts
index 07d6ded..4c51744 100644
--- a/src/services/schemes.ts
+++ b/src/services/schemes.ts
@@ -1,3 +1,4 @@
+import { basename } from "@/utils/strings";
import { execAsync, GLib, GObject, monitorFile, property, readFileAsync, register } from "astal";
import type { IPalette } from "./palette";
@@ -19,10 +20,6 @@ export default class Schemes extends GObject.Object {
return this.#map;
}
- #schemePathToName(path: string) {
- return path.slice(path.lastIndexOf("/") + 1, path.lastIndexOf("."));
- }
-
async parseScheme(path: string) {
const schemeColours = (await readFileAsync(path)).split("\n").map(l => l.split(" "));
return schemeColours.reduce((acc, [name, hex]) => ({ ...acc, [name]: `#${hex}` }), {} as IPalette);
@@ -30,8 +27,7 @@ export default class Schemes extends GObject.Object {
async update() {
const schemes = await execAsync(`find ${DATA}/scripts/data/schemes/ -type f`);
- for (const scheme of schemes.split("\n"))
- this.#map[this.#schemePathToName(scheme)] = await this.parseScheme(scheme);
+ for (const scheme of schemes.split("\n")) this.#map[basename(scheme)] = await this.parseScheme(scheme);
this.notify("map");
}
diff --git a/src/services/wallpapers.ts b/src/services/wallpapers.ts
new file mode 100644
index 0000000..e7a8742
--- /dev/null
+++ b/src/services/wallpapers.ts
@@ -0,0 +1,81 @@
+import { basename } from "@/utils/strings";
+import { execAsync, Gio, GLib, GObject, property, register } from "astal";
+import { wallpapers as config } from "config";
+
+export interface Wallpaper {
+ path: string;
+ thumbnail?: string;
+}
+
+@register({ GTypeName: "Wallpapers" })
+export default class Wallpapers extends GObject.Object {
+ static instance: Wallpapers;
+ static get_default() {
+ if (!this.instance) this.instance = new Wallpapers();
+
+ return this.instance;
+ }
+
+ #thumbnailDir = `${CACHE}/thumbnails`;
+
+ #list: Wallpaper[] = [];
+
+ @property(Object)
+ get list() {
+ return this.#list;
+ }
+
+ async #thumbnail(path: string) {
+ const thumbPath = `${this.#thumbnailDir}/${basename(path)}.jpg`;
+ await execAsync(`magick -define jpeg:size=1000x500 ${path} -thumbnail 500x250 -unsharp 0x.5 ${thumbPath}`);
+ return thumbPath;
+ }
+
+ async update() {
+ const results = await Promise.allSettled(
+ config.paths
+ .get()
+ .map(p => execAsync(`find ${p.path.replace("~", HOME)}/ ${p.recursive ? "" : "-maxdepth 1"} -type f`))
+ );
+ const files = results
+ .filter(r => r.status === "fulfilled")
+ .map(r => r.value.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.notify("list");
+ }
+
+ constructor() {
+ super();
+
+ GLib.mkdir_with_parents(this.#thumbnailDir, 0o755);
+
+ this.update().catch(console.error);
+
+ const monitorDir = ({ path, recursive }: { path: string; recursive: boolean }) => {
+ const file = Gio.file_new_for_path(path.replace("~", HOME));
+ const monitor = file.monitor_directory(null, null);
+ monitor.connect("changed", () => this.update().catch(console.error));
+
+ const monitors = [monitor];
+
+ if (recursive) {
+ const enumerator = file.enumerate_children("standard::*", null, null);
+ let child;
+ while ((child = enumerator.next_file(null)))
+ if (child.get_file_type() === Gio.FileType.DIRECTORY)
+ monitors.push(...monitorDir({ path: `${path}/${child.get_name()}`, recursive }));
+ }
+
+ return monitors;
+ };
+
+ let monitors = config.paths.get().flatMap(monitorDir);
+ config.paths.subscribe(v => {
+ for (const m of monitors) m.cancel();
+ monitors = v.flatMap(monitorDir);
+ });
+ }
+}
diff --git a/src/utils/strings.ts b/src/utils/strings.ts
index df2f781..2fd4c76 100644
--- a/src/utils/strings.ts
+++ b/src/utils/strings.ts
@@ -1 +1,3 @@
export const ellipsize = (str: string, len: number) => (str.length > len ? `${str.slice(0, len - 1)}…` : str);
+
+export const basename = (path: string) => path.slice(path.lastIndexOf("/") + 1, path.lastIndexOf("."));