feat: make AGS colorshell configuration fully declarative

- Add complete colorshell v2.0.3 configuration to home/ags-config/
- Disable runner plugin and NightLight tile (incompatible with NixOS)
- Customize SCSS with full opacity (no transparency)
- Add dark pale blue color scheme in home/pywal-colors/
- Configure Papirus-Dark icon theme via home-manager
- Make ~/.config/ags/ immutable and managed by Nix store
- Auto-deploy pywal colors to ~/.cache/wal/colors.json

All AGS configuration is now reproducible and version controlled.
This commit is contained in:
2025-11-04 21:36:38 +00:00
parent 593735370a
commit b2ae32a078
240 changed files with 1024921 additions and 3 deletions

344
home/ags-config/app.ts Normal file
View File

@@ -0,0 +1,344 @@
// thanks Aylur!!
// import "/usr/share/ags/js/lib/overrides"; // Not needed in AGS v3 with NixOS
// Define build-time constants (normally set by colorshell's build script)
globalThis.DEVEL = false;
globalThis.SRC = "$HOME/.config/ags";
globalThis.GRESOURCES_FILE = "$HOME/.config/ags/resources.gresource";
globalThis.COLORSHELL_VERSION = "2.0.3-nixos";
import "./config";
// Runner disabled - using Vicinae instead
// import {
// PluginApps,
// PluginClipboard,
// PluginMedia,
// PluginShell,
// PluginWallpapers,
// PluginWebSearch,
// PluginKill
// } from "./runner/plugins";
import { handleArguments } from "./modules/arg-handler";
// import { Runner } from "./runner/Runner";
import { Windows } from "./windows";
import { Notifications } from "./modules/notifications";
import { Wallpaper } from "./modules/wallpaper";
import { Stylesheet } from "./modules/stylesheet";
import { Clipboard } from "./modules/clipboard";
import { Gdk, Gtk } from "ags/gtk4";
import { createBinding, createComputed, createRoot, getScope, Scope } from "ags";
import { OSDModes, triggerOSD } from "./window/osd";
import { programArgs, programInvocationName } from "system";
import { setConsoleLogDomain } from "console";
import { createScopedConnection, createSubscription, encoder, secureBaseBinding } from "./modules/utils";
import { exec } from "ags/process";
import { NightLight } from "./modules/nightlight";
import { Backlights } from "./modules/backlight";
import GObject, { register } from "ags/gobject";
import Media from "./modules/media";
import GLib from "gi://GLib?version=2.0";
import Gio from "gi://Gio?version=2.0";
import Adw from "gi://Adw?version=1";
import AstalWp from "gi://AstalWp";
// const runnerPlugins: Array<Runner.Plugin> = [
// PluginApps,
// PluginShell,
// PluginWebSearch,
// PluginKill,
// PluginMedia,
// PluginWallpapers,
// PluginClipboard
// ];
const defaultWindows: Array<string> = [ "bar" ];
GLib.unsetenv("LD_PRELOAD");
@register({ GTypeName: "Shell" })
export class Shell extends Adw.Application {
private static instance: Shell;
#scope!: Scope;
#connections = new Map<GObject.Object, Array<number> | number>();
#providers: Array<Gtk.CssProvider> = [];
#gresource: Gio.Resource|null = null;
#socketService!: Gio.SocketService;
#socketFile!: Gio.File;
get scope() { return this.#scope; }
constructor() {
super({
applicationId: "io.github.retrozinndev.colorshell",
flags: Gio.ApplicationFlags.HANDLES_COMMAND_LINE,
version: COLORSHELL_VERSION ?? "0.0.0-unknown",
});
setConsoleLogDomain("Colorshell");
GLib.set_application_name("colorshell");
}
public static getDefault(): Shell {
if(!this.instance)
this.instance = new Shell();
return this.instance;
}
public resetStyle(): void {
this.#providers.forEach(provider =>
Gtk.StyleContext.remove_provider_for_display(
Gdk.Display.get_default()!,
provider
)
);
}
public removeProvider(provider: Gtk.CssProvider): void {
if(!this.#providers.includes(provider)) {
console.warn("Colorshell: Couldn't find the provided GtkCssProvider to remove. Was it added before?");
return;
}
for(let i = 0; i < this.#providers.length; i++) {
const prov = this.#providers[i];
if(prov === provider) {
this.#providers.splice(i, 1);
Gtk.StyleContext.remove_provider_for_display(
Gdk.Display.get_default()!,
provider
);
break;
}
}
}
public applyStyle(stylesheet: string): void {
try {
const provider = Gtk.CssProvider.new();
provider.load_from_string(stylesheet)
this.#providers.push(provider);
Gtk.StyleContext.add_provider_for_display(
Gdk.Display.get_default()!,
provider,
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
);
} catch(e) {
console.error(`Colorshell: Couldn't apply style. Stderr: ${e}`);
return;
}
}
vfunc_command_line(cmd: Gio.ApplicationCommandLine): number {
const args = cmd.get_arguments().toSpliced(0, 1); // remove executable
if(cmd.isRemote) {
try {
// warn user that this method is pretty slow
cmd.print_literal("\nColorshell: !! Using a remote instance to communicate is pretty slow, \
you should use the socket in the XDG_RUNTIME_DIR/colorshell.sock for a faster response.\n\n");
const res = handleArguments(cmd, args);
cmd.done();
cmd.set_exit_status(res);
return res;
} catch(_e) {
const e = _e as Error;
cmd.printerr_literal(`Error: something went wrong! Stderr: ${e.message}\n${e.stack}`);
cmd.done();
return 1;
}
} else {
if(args.length > 0) {
cmd.printerr_literal("Error: colorshell not running. Try to clean-run before using arguments");
cmd.done();
return 1;
}
this.activate();
}
return 0;
}
vfunc_activate(): void {
super.vfunc_activate();
this.hold();
this.main();
}
private init(): void {
// load gresource from build-defined path
try {
this.#gresource = Gio.Resource.load(GRESOURCES_FILE.split('/').filter(s =>
s !== ""
).map(path => {
// support environment variables at runtime
if(/^\$/.test(path)) {
const env = GLib.getenv(path.replace(/^\$/, ""));
if(env === null)
throw new Error(`Couldn't get environment variable: ${path}`);
return env;
}
return path;
}).join('/'));
Gio.resources_register(this.#gresource);
// add icons
Gtk.IconTheme.get_for_display(Gdk.Display.get_default()!)
.add_resource_path("/io/github/retrozinndev/colorshell/icons")
} catch(_e) {
const e = _e as Error;
console.error(`Error: couldn't load gresource! Stderr: ${e.message}\n${e.stack}`);
}
this.#socketFile = Gio.File.new_for_path(`${GLib.get_user_runtime_dir() ??
`/run/user/${exec("id -u").trim()}`}/colorshell.sock`);
if(this.#socketFile.query_exists(null)) {
console.log(`Colorshell: Deleting previous instance's socket`);
this.#socketFile.delete(null);
}
this.#socketService = Gio.SocketService.new();
this.#socketService.add_address(
Gio.UnixSocketAddress.new(this.#socketFile.get_path()!),
Gio.SocketType.STREAM,
Gio.SocketProtocol.DEFAULT,
null
);
// handle communication via socket
createScopedConnection(this.#socketService, "incoming", (conn) => {
const inputStream = Gio.DataInputStream.new(conn.inputStream);
inputStream.read_upto_async('\x00', -1, GLib.PRIORITY_DEFAULT, null, (_, res) => {
const [args, len] = inputStream.read_upto_finish(res);
inputStream.close(null);
conn.inputStream.close(null);
if(len < 1) {
console.error(`Colorshell: No args provided via socket call`);
return;
}
try {
const [success, parsedArgs] = GLib.shell_parse_argv(`colorshell ${args}`);
parsedArgs?.splice(0, 1); // remove the unnecessary `colorshell` part
if(success) {
handleArguments({
print_literal: (msg) => conn.outputStream.write_bytes(
encoder.encode(`${msg}\n`),
null
),
// TODO: support writing to stderr(i don't know how to do that :sob:)
printerr_literal: (msg) => conn.outputStream.write_bytes(
encoder.encode(`${msg}\n`),
null
)
}, parsedArgs!);
conn.outputStream.flush(null);
conn.close(null);
return;
}
conn.outputStream.write_bytes(
encoder.encode("Error: Unexpected error occurred on argument parsing!"),
null
);
conn.outputStream.flush(null);
conn.close(null);
} catch(_e) {
const e = _e as Error;
console.error(`Colorshell: An error occurred while writing to socket output. Stderr:\n${
e.message}\n${e.stack}`);
}
});
return false;
});
}
private main(): void {
Gtk.init();
Adw.init();
createRoot((dispose) => {
console.log(`Colorshell: Initializing things`);
this.#connections.set(this, this.connect("shutdown", () => dispose()));
this.init();
this.#scope = getScope();
// NightLight.getDefault(); // Disabled - requires gamma control tools
Media.getDefault();
Clipboard.getDefault();
console.log("Colorshell: Initializing Wallpaper and Stylesheet modules");
Wallpaper.getDefault();
Stylesheet.getDefault();
// Runner disabled - using Vicinae instead
// console.log("Adding runner plugins");
// runnerPlugins.forEach(plugin => Runner.addPlugin(plugin));
createSubscription(
createComputed([
secureBaseBinding<AstalWp.Endpoint>(createBinding(
AstalWp.get_default(), "defaultSpeaker"
), "volume", null),
secureBaseBinding<AstalWp.Endpoint>(createBinding(
AstalWp.get_default(), "defaultSpeaker"
), "mute", null)
]),
() => !Windows.getDefault().isOpen("control-center") &&
triggerOSD(OSDModes.sink)
);
createSubscription(
secureBaseBinding<Backlights.Backlight>(
createBinding(Backlights.getDefault(), "default"),
"brightness",
100
),
() => !Windows.getDefault().isOpen("control-center") &&
triggerOSD(OSDModes.brightness)
);
this.#connections.set(Notifications.getDefault(), [
Notifications.getDefault().connect("notification-added", () => {
Windows.getDefault().open("floating-notifications");
}),
Notifications.getDefault().connect("notification-removed", (self) => {
self.notifications.length === 0 && Windows.getDefault().close("floating-notifications");
})
]);
defaultWindows.forEach(w => Windows.getDefault().open(w));
});
this.#scope.onCleanup(() => {
console.log("Colorshell: disposing connections and quitting because of ::shutdown");
this.#connections.forEach((ids, obj) => Array.isArray(ids) ?
ids.forEach(id => obj.disconnect(id))
: obj.disconnect(ids));
});
}
quit(): void {
this.release();
}
}
Shell.getDefault().runAsync([ programInvocationName, ...programArgs ]);