This commit is contained in:
2025-12-01 06:45:17 -04:00
parent f50cc9928f
commit c8d6711466
17 changed files with 298 additions and 100 deletions
+3 -3
View File
@@ -37,7 +37,7 @@ Audio Controls:
Media Controls:
media: manage colorshell's active player, see "media help".
${DEVEL ? `
${(typeof DEVEL !== "undefined" && DEVEL) ? `
Development Tools:
dev: tools to help debugging colorshell
` : ""}
@@ -60,8 +60,8 @@ export function handleArguments(cmd: RemoteCaller, args: Array<string>): number
case "version":
case "v":
cmd.print_literal(`colorshell by retrozinndev, version ${COLORSHELL_VERSION
}${DEVEL ? " (devel)" : ""}\nhttps://github.com/retrozinndev/colorshell`);
cmd.print_literal(`colorshell by retrozinndev, version ${(typeof COLORSHELL_VERSION !== "undefined" ? COLORSHELL_VERSION : "unknown")
}${(typeof DEVEL !== "undefined" && DEVEL) ? " (devel)" : ""}\nhttps://github.com/retrozinndev/colorshell`);
return 0;
case "dev":
+14 -2
View File
@@ -120,7 +120,13 @@ export class NightLight extends GObject.Object {
}
public applyIdentity(): void {
this.dispatch("identity");
try {
this.dispatch("identity");
} catch (e) {
// hyprsunset not available, skip
console.warn("Night Light: hyprsunset not available, cannot apply identity");
return;
}
if(!this.#identity) {
this.#identity = true;
@@ -133,7 +139,13 @@ export class NightLight extends GObject.Object {
private dispatch(call: "identity"): string;
private dispatch(call: "temperature"|"gamma"|"identity", val?: number): string {
return exec(`hyprctl hyprsunset ${call}${val != null ? ` ${val}` : ""}`);
try {
return exec(`hyprctl hyprsunset ${call}${val != null ? ` ${val}` : ""}`);
} catch (e) {
// hyprsunset not available, return empty string
console.warn(`Night Light: hyprsunset not available, skipping ${call} command`);
return "";
}
}
private async dispatchAsync(call: "temperature", val: number): Promise<string>;
+107 -33
View File
@@ -4,7 +4,7 @@ import GObject, { register, getter, gtype, property, setter } from "ags/gobject"
import Gio from "gi://Gio?version=2.0";
import GLib from "gi://GLib?version=2.0";
import { createSubscription, encoder } from "./utils";
import { createSubscription } from "./utils";
import { Notifications } from "./notifications";
import { generalConfig } from "../config";
import { createRoot, getScope, Scope } from "ags";
@@ -161,37 +161,45 @@ class Wallpaper extends GObject.Object {
return this.instance;
}
private writeChanges(): void {
this.#hyprpaperFile.replace_async(null, false,
Gio.FileCreateFlags.REPLACE_DESTINATION,
GLib.PRIORITY_DEFAULT, null, (_, result) => {
const res = this.#hyprpaperFile.replace_finish(result);
if(!res) {
console.error(`Wallpaper: an error occurred when trying to replace the hyprpaper file`);
private writeChanges(): Promise<void> {
return new Promise((resolve, reject) => {
try {
const content = `# This file was automatically generated by colorshell
preload = ${this.#wallpaper}
splash = ${this.#splash}
wallpaper = , ${this.positioning === "cover" ? "" : `${this.positioning}:`}${this.#wallpaper}
`;
// Use synchronous file writing for reliability
const filePath = this.#hyprpaperFile.get_path();
if(!filePath) {
reject(new Error("Could not get hyprpaper file path"));
return;
}
// success
res.write_bytes_async(encoder.encode(`\
# This file was automatically generated by colorshell
// Ensure directory exists
const parentDir = this.#hyprpaperFile.get_parent();
if(parentDir && !parentDir.query_exists(null)) {
parentDir.make_directory_with_parents(null);
}
preload = ${this.#wallpaper}
splash = ${this.#splash}
wallpaper = , ${this.positioning === "cover" ? "" : `${this.positioning}:`}${
this.#wallpaper}`.split('\n').map(str => str.trimStart()).join('\n')),
GLib.PRIORITY_DEFAULT, null, (_, asyncRes) => {
if(_!.write_finish(asyncRes)) res.flush(null);
res.close(null);
}
);
return;
// Write file synchronously using GLib
const success = GLib.file_set_contents(filePath, content);
if(success) {
resolve();
} else {
reject(new Error("Failed to write hyprpaper config file"));
}
} catch (e: any) {
reject(new Error(`Failed to write config file: ${e.message}`));
}
);
});
}
public getData(): WalData {
const content = readFile(`${GLib.getenv("XDG_CACHE_HOME")}/wal/colors.json`);
const cacheHome = GLib.getenv("XDG_CACHE_HOME") || `${GLib.get_home_dir()}/.cache`;
const content = readFile(`${cacheHome}/wal/colors.json`);
return JSON.parse(content) as WalData;
}
@@ -210,10 +218,41 @@ class Wallpaper extends GObject.Object {
}
public reloadColors(): void {
execAsync(`wal -t --cols16 ${this.colorMode} -i "${this.#wallpaper}"`).then(() => {
const cacheHome = GLib.getenv("XDG_CACHE_HOME") || `${GLib.get_home_dir()}/.cache`;
const colorsKittyPath = `${cacheHome}/wal/colors-kitty.conf`;
const kittyConfigPath = `${GLib.get_user_config_dir()}/kitty/kitty.conf`;
const runWal = (extraArgs: string = "") =>
execAsync(`wal -t --cols16 ${this.colorMode} ${extraArgs} -i "${this.#wallpaper}"`);
// First try default backend; if it fails (e.g. some images on aarch64),
// fall back to a more forgiving backend like "colorz".
runWal().catch((e: Error) => {
console.error(`Wallpaper: Couldn't update shell colors with default backend. Stderr: ${e.message}`);
console.log("Wallpaper: Falling back to pywal backend 'colorz'");
return runWal("--backend colorz");
}).then(() => {
console.log("Wallpaper: reloaded shell colors");
// First, try to set colors on all existing kitty instances
execAsync(`kitty @ set-colors --all ${colorsKittyPath}`).then(() => {
console.log("Wallpaper: reloaded colors in existing kitty instances");
}).catch((e: Error) => {
// It's okay if this fails (e.g., no kitty instances running)
console.log(`Wallpaper: Couldn't reload kitty colors in existing instances: ${e.message}`);
});
// Then, update the configured colors for future kitty instances
// This is critical - it tells kitty to use these colors for new windows
execAsync(`kitty @ set-colors --configured ${colorsKittyPath}`).then(() => {
console.log("Wallpaper: configured colors for future kitty instances");
}).catch((e: Error) => {
// If no kitty instances are running, we can't set configured colors
// In this case, new instances should still pick up colors from the include directive
console.log(`Wallpaper: Couldn't set configured colors (new instances will use include directive): ${e.message}`);
});
}).catch((e: Error) => {
console.error(`Wallpaper: Couldn't update shell colors. Stderr: ${e.message}`);
console.error(`Wallpaper: Couldn't update shell colors even with fallback backend. Stderr: ${e.message}`);
});
}
@@ -221,10 +260,39 @@ class Wallpaper extends GObject.Object {
if(this.wallpaper.trim() === "")
return;
await execAsync(`hyprctl hyprpaper reload \", ${
this.positioning === "cover" ? "" : `${this.positioning}:`
}${this.wallpaper}\"`);
write && this.writeChanges();
const wallpaperPath = this.#wallpaper.trim();
try {
// Write config file first if needed
if(write) {
await this.writeChanges();
}
// Unload all current wallpapers
await execAsync(`hyprctl hyprpaper unload all`).catch(() => {
// Ignore errors - this is usually fine
});
// Preload the new wallpaper
await execAsync(`hyprctl hyprpaper preload "${wallpaperPath}"`);
// Set wallpaper on all monitors
await execAsync(`hyprctl hyprpaper wallpaper ", ${wallpaperPath}"`);
// Note: We don't need to reload or restart hyprpaper here
// The preload and wallpaper commands should apply the change immediately
// The config file is written for persistence across hyprpaper restarts
} catch (e: any) {
console.error(`Wallpaper: Error reloading wallpaper: ${e.message}`);
console.error(`Wallpaper: Stack trace: ${e.stack}`);
Notifications.getDefault().sendNotification({
appName: "colorshell",
summary: "Failed to set wallpaper",
body: `Error: ${e.message}`
});
throw e;
}
}
public setWallpaper(path: string|Gio.File, write: boolean = true): void {
@@ -244,10 +312,16 @@ class Wallpaper extends GObject.Object {
public async pickWallpaper(): Promise<string|undefined> {
return (await execAsync(`zenity --file-selection`).then(wall => {
if(!wall.trim()) return undefined;
const trimmedWall = wall.trim();
if(!trimmedWall) return undefined;
this.setWallpaper(wall);
return wall;
// Ensure path is absolute
const absolutePath = GLib.path_is_absolute(trimmedWall)
? trimmedWall
: GLib.build_filenamev([GLib.get_current_dir(), trimmedWall]);
this.setWallpaper(absolutePath);
return absolutePath;
}).catch((e: Error) => {
console.error(`Wallpaper: Couldn't pick wallpaper, is \`zenity\` installed? Stderr: ${e.message}`);
return undefined;