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

View File

@@ -0,0 +1,264 @@
import { Scope } from "ags";
import { createScopedConnection, decoder, encoder } from "../modules/utils";
import { showWorkspaceNumber } from "../window/bar/widgets/Workspaces";
import windows from "./modules/windows";
import volume from "./modules/volume";
import devel from "./modules/devel";
import media from "./modules/media";
import Gio from "gi://Gio?version=2.0";
import GLib from "gi://GLib?version=2.0";
/** cli implementation for colorshell */
export namespace Cli {
let rootScope: Scope;
let initialized: boolean = false;
const modules: Array<Module> = [
// main module, no need for prefix
{
help: "manage colorshell windows and do more cool stuff.",
commands: [
...windows,
// others
{
name: "runner",
onCalled: (_, data) => {
return {
content: `Opening runner${data ? ` with "${data}"` : ""}...`,
type: "out"
};
}
},
{
name: "peek-workspace-num",
help: "peek the workspace numbers in the workspace indicator. (optional: time in millis)",
onCalled: () => {
showWorkspaceNumber(true);
return "Peeking workspace IDs...";
}
}
],
arguments: [
{
name: "version",
alias: "v",
help: "print the current colorshell version",
onCalled: () => `colorshell by retrozinndev, version ${COLORSHELL_VERSION
}${DEVEL ? "(devel)" : ""}`
}
]
},
volume,
media
];
export type Output = {
type: "err"|"out";
content: string|Uint8Array;
} | string;
/** argument passed to the command / module.
* output of onCalled is passed to */
export type Argument = {
/** kebab-cased name for the argument(without the `--` prefix)
* @example help (turns into `--help` internally)*/
name: string;
/** alias for the name (without the `-` prefix).
* @example help -> h */
alias?: string;
/** whether the argument needs a value attribute.
* @example --file ~/a_nice_home_file.txt */
hasValue?: boolean;
/** runtime-set value for the argument(if enabled) */
value?: string;
/** help message for the argument */
help?: string;
onCalled?: (value?: string) => void;
};
export type ArgumentData = {
argument: Argument;
data?: string;
};
export type Command = {
/** the command name to be called.
* @example `colorshell ${prefix?} ${command.name}`*/
name: string;
help?: string;
/** data passed to the command. (only works when arguments are disabled) */
data?: string;
arguments?: Array<Argument>;
onCalled: (args: Array<ArgumentData>, data?: string) => Output;
};
export type Module = {
/** command to come after the cli call.
* @example `colorshell ${prefix?} ${command}`*/
prefix?: string;
commands?: Array<Command>;
arguments?: Array<Argument>;
help?: string;
/** called everytime the prefix is used, even when using module commands */
onPrefixCalled?: () => void;
};
/** initialize the cli */
export function init(scope: Scope, communicationMethod: Gio.SocketService|Gio.ApplicationCommandLine, app?: Gio.Application): void {
if(initialized) return;
initialized = true;
rootScope = scope;
DEVEL && modules.push(devel);
scope.run(() => {
if(communicationMethod instanceof Gio.SocketService) {
createScopedConnection(
communicationMethod, "incoming", (conn) => {
try {
return handleIncoming(conn);
} catch(_) {}
return false;
}
);
return;
}
if(!app)
throw new Error("GApplication not specified for GApplicationCommandLine communication method")
if(app.flags !& Gio.ApplicationFlags.HANDLES_COMMAND_LINE)
throw new Error("GApplication does not have the HANDLES_COMMAND_LINE flag or doesn't implement it")
createScopedConnection(
app,
"command-line",
(cmd) => {
let hasError: boolean = false;
try {
handleArgs(
cmd.get_arguments().toSpliced(0, 1),
(str, type) => {
if(type === "err") {
cmd.printerr_literal(str);
hasError = true;
return;
}
cmd.print_literal(str);
}
);
} catch(_) {
// TODO better error message
hasError = true;
}
return hasError ? 1 : 0;
}
);
});
}
/** handle incoming socket calls */
function handleIncoming(conn: Gio.SocketConnection): void {
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) {
handleArgs(parsedArgs!, conn.outputStream);
conn.outputStream.flush(null);
conn.close(null);
return;
}
conn.outputStream.write_bytes(
encoder.encode("Error: Unexpected syntax error occurred"),
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}`);
}
});
}
/** translate app arguments to modules/commands
* order: module ?arg -> command ?arg */
function handleArgs(args: Array<string>, writeTo: Gio.OutputStream|((str: string, type: "out"|"err") => void)): void {
let mod: Module;
let command: Command|undefined;
const modArgs: Array<Argument> = [];
const cmdArgs: Array<Argument> = [];
function print(out: Output): void {
const content = `${outputToString(out)}\n`;
const type: "out"|"err" = typeof out === "object" ?
out.type
: "out";
typeof writeTo === "function" ?
writeTo(content, type)
: writeTo.write_bytes(
encoder.encode(`${outputToString(out)}\n`),
null
);
}
function handleCommandArguments(cmd: Module|Command, args: Array<string>, index: number, printFun: (out: Output) => void): void {
const argNameRegEx = /^--/, argAliasRegEx = /^-/;
let argName: string;
if(args[index].startsWith("--")) {
}
}
const firstFoundMod = modules.filter(mod => mod.prefix === args[0])[0];
mod = firstFoundMod ?? modules[0];
if(!mod) {
print({
content: `No command module found with the name ${args[0]}!`,
type: "err"
});
return;
}
for(let i = 1; i < args.length; i++) {
const arg = args[i];
if(/^-/.test(arg)) {
handleCommandArguments(command ?? mod, args, i, print);
continue;
}
}
function outputToString(out: Output): string {
if(typeof out === "object")
return out.content instanceof Uint8Array ?
decoder.decode(out.content)
: out.content;
return out;
}
}