launcher: wallpaper action
This commit is contained in:
parent
31138ffd93
commit
9c1cc00965
7 changed files with 173 additions and 7 deletions
2
app.tsx
2
app.tsx
|
|
@ -8,6 +8,7 @@ import Monitors from "@/services/monitors";
|
||||||
import Palette from "@/services/palette";
|
import Palette from "@/services/palette";
|
||||||
import Players from "@/services/players";
|
import Players from "@/services/players";
|
||||||
import Schemes from "@/services/schemes";
|
import Schemes from "@/services/schemes";
|
||||||
|
import Wallpapers from "@/services/wallpapers";
|
||||||
import type PopupWindow from "@/widgets/popupwindow";
|
import type PopupWindow from "@/widgets/popupwindow";
|
||||||
import { execAsync, idle, writeFileAsync } from "astal";
|
import { execAsync, idle, writeFileAsync } from "astal";
|
||||||
import { App } from "astal/gtk3";
|
import { App } from "astal/gtk3";
|
||||||
|
|
@ -41,6 +42,7 @@ App.start({
|
||||||
|
|
||||||
// Init services
|
// Init services
|
||||||
idle(() => Schemes.get_default());
|
idle(() => Schemes.get_default());
|
||||||
|
idle(() => Wallpapers.get_default());
|
||||||
|
|
||||||
console.log(`Caelestia started in ${Date.now() - now}ms`);
|
console.log(`Caelestia started in ${Date.now() - now}ms`);
|
||||||
},
|
},
|
||||||
|
|
|
||||||
27
config.ts
27
config.ts
|
|
@ -121,6 +121,9 @@ const DEFAULTS = {
|
||||||
todo: {
|
todo: {
|
||||||
notify: true,
|
notify: true,
|
||||||
},
|
},
|
||||||
|
wallpaper: {
|
||||||
|
style: "compact", // One of "compact", "medium", "large"
|
||||||
|
},
|
||||||
},
|
},
|
||||||
notifpopups: {
|
notifpopups: {
|
||||||
maxPopups: -1,
|
maxPopups: -1,
|
||||||
|
|
@ -190,8 +193,30 @@ const DEFAULTS = {
|
||||||
storage: {
|
storage: {
|
||||||
interval: 5000,
|
interval: 5000,
|
||||||
},
|
},
|
||||||
|
wallpapers: {
|
||||||
|
paths: [
|
||||||
|
{
|
||||||
|
recursive: true, // Whether to search recursively
|
||||||
|
path: "~/Pictures/Wallpapers", // Path to search
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const config = convertSettings(DEFAULTS);
|
const config = convertSettings(DEFAULTS);
|
||||||
|
|
||||||
export const { bar, launcher, notifpopups, osds, sideleft, math, updates, weather, cpu, gpu, memory, storage } = config;
|
export const {
|
||||||
|
bar,
|
||||||
|
launcher,
|
||||||
|
notifpopups,
|
||||||
|
osds,
|
||||||
|
sideleft,
|
||||||
|
math,
|
||||||
|
updates,
|
||||||
|
weather,
|
||||||
|
cpu,
|
||||||
|
gpu,
|
||||||
|
memory,
|
||||||
|
storage,
|
||||||
|
wallpapers,
|
||||||
|
} = config;
|
||||||
|
|
|
||||||
|
|
@ -153,4 +153,36 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.wallpaper {
|
||||||
|
margin: lib.s(3) 0;
|
||||||
|
|
||||||
|
.thumbnail {
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.compact .thumbnail {
|
||||||
|
@include lib.rounded(100);
|
||||||
|
|
||||||
|
min-width: lib.s(32);
|
||||||
|
min-height: lib.s(32);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(.compact) {
|
||||||
|
@include lib.spacing(3, true);
|
||||||
|
|
||||||
|
.thumbnail {
|
||||||
|
@include lib.rounded(10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.medium .thumbnail {
|
||||||
|
min-height: lib.s(96);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.large .thumbnail {
|
||||||
|
min-height: lib.s(160);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
import { Apps } from "@/services/apps";
|
import { Apps } from "@/services/apps";
|
||||||
import type { IPalette } from "@/services/palette";
|
import type { IPalette } from "@/services/palette";
|
||||||
import Schemes from "@/services/schemes";
|
import Schemes from "@/services/schemes";
|
||||||
|
import Wallpapers from "@/services/wallpapers";
|
||||||
|
import { basename } from "@/utils/strings";
|
||||||
import { notify } from "@/utils/system";
|
import { notify } from "@/utils/system";
|
||||||
import { setupCustomTooltip, type FlowBox } from "@/utils/widgets";
|
import { setupCustomTooltip, type FlowBox } from "@/utils/widgets";
|
||||||
import { execAsync, GLib, readFile, register, type Variable } from "astal";
|
import { execAsync, GLib, readFile, register, type Variable } from "astal";
|
||||||
|
|
@ -218,6 +220,28 @@ const Scheme = ({ name, colours }: { name: string; colours: IPalette }) => (
|
||||||
</Gtk.FlowBoxChild>
|
</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()
|
@register()
|
||||||
export default class Actions extends Widget.Box implements LauncherContent {
|
export default class Actions extends Widget.Box implements LauncherContent {
|
||||||
#map: ActionMap;
|
#map: ActionMap;
|
||||||
|
|
@ -249,6 +273,10 @@ export default class Actions extends Widget.Box implements LauncherContent {
|
||||||
const scheme = args[1] ?? "";
|
const scheme = args[1] ?? "";
|
||||||
for (const { target } of fuzzysort.go(scheme, Object.keys(Schemes.get_default().map), { all: true }))
|
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]} />);
|
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 {
|
} else {
|
||||||
for (const { target } of fuzzysort.go(action, this.#list, { all: true }))
|
for (const { target } of fuzzysort.go(action, this.#list, { all: true }))
|
||||||
this.#content.add(<Action {...this.#map[target]} args={args.slice(1)} />);
|
this.#content.add(<Action {...this.#map[target]} args={args.slice(1)} />);
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { basename } from "@/utils/strings";
|
||||||
import { execAsync, GLib, GObject, monitorFile, property, readFileAsync, register } from "astal";
|
import { execAsync, GLib, GObject, monitorFile, property, readFileAsync, register } from "astal";
|
||||||
import type { IPalette } from "./palette";
|
import type { IPalette } from "./palette";
|
||||||
|
|
||||||
|
|
@ -19,10 +20,6 @@ export default class Schemes extends GObject.Object {
|
||||||
return this.#map;
|
return this.#map;
|
||||||
}
|
}
|
||||||
|
|
||||||
#schemePathToName(path: string) {
|
|
||||||
return path.slice(path.lastIndexOf("/") + 1, path.lastIndexOf("."));
|
|
||||||
}
|
|
||||||
|
|
||||||
async parseScheme(path: string) {
|
async parseScheme(path: string) {
|
||||||
const schemeColours = (await readFileAsync(path)).split("\n").map(l => l.split(" "));
|
const schemeColours = (await readFileAsync(path)).split("\n").map(l => l.split(" "));
|
||||||
return schemeColours.reduce((acc, [name, hex]) => ({ ...acc, [name]: `#${hex}` }), {} as IPalette);
|
return schemeColours.reduce((acc, [name, hex]) => ({ ...acc, [name]: `#${hex}` }), {} as IPalette);
|
||||||
|
|
@ -30,8 +27,7 @@ export default class Schemes extends GObject.Object {
|
||||||
|
|
||||||
async update() {
|
async update() {
|
||||||
const schemes = await execAsync(`find ${DATA}/scripts/data/schemes/ -type f`);
|
const schemes = await execAsync(`find ${DATA}/scripts/data/schemes/ -type f`);
|
||||||
for (const scheme of schemes.split("\n"))
|
for (const scheme of schemes.split("\n")) this.#map[basename(scheme)] = await this.parseScheme(scheme);
|
||||||
this.#map[this.#schemePathToName(scheme)] = await this.parseScheme(scheme);
|
|
||||||
this.notify("map");
|
this.notify("map");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
81
src/services/wallpapers.ts
Normal file
81
src/services/wallpapers.ts
Normal file
|
|
@ -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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1 +1,3 @@
|
||||||
export const ellipsize = (str: string, len: number) => (str.length > len ? `${str.slice(0, len - 1)}…` : str);
|
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("."));
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue