🔧 chore: start new cli development, use pnpm's built-in github repo feature for gnim-utils

This commit is contained in:
retrozinndev
2025-10-07 10:04:09 -03:00
parent 1996ab88b8
commit 14a3855df8
12 changed files with 335 additions and 36 deletions
-3
View File
@@ -1,3 +0,0 @@
[submodule "gnim-utils"]
path = src/utils
url = https://github.com/retrozinndev/gnim-utils.git
+3
View File
@@ -14,5 +14,8 @@
}, },
"devDependencies": { "devDependencies": {
"ags": "link:../../../../usr/share/ags/js" "ags": "link:../../../../usr/share/ags/js"
},
"dependencies": {
"gnim-utils": "github:retrozinndev/gnim-utils"
} }
} }
+21 -7
View File
@@ -1,4 +1,5 @@
import "ags/overrides"; // thanks Aylur!!
import "../node_modules/ags/lib/overrides";
import "./config"; import "./config";
import { import {
PluginApps, PluginApps,
@@ -9,7 +10,6 @@ import {
PluginWebSearch, PluginWebSearch,
PluginKill PluginKill
} from "./runner/plugins"; } from "./runner/plugins";
import { Wireplumber } from "./modules/volume";
import { handleArguments } from "./modules/arg-handler"; import { handleArguments } from "./modules/arg-handler";
import { Runner } from "./runner/Runner"; import { Runner } from "./runner/Runner";
import { Windows } from "./windows"; import { Windows } from "./windows";
@@ -32,6 +32,7 @@ import GObject, { register } from "ags/gobject";
import GLib from "gi://GLib?version=2.0"; import GLib from "gi://GLib?version=2.0";
import Gio from "gi://Gio?version=2.0"; import Gio from "gi://Gio?version=2.0";
import Adw from "gi://Adw?version=1"; import Adw from "gi://Adw?version=1";
import AstalWp from "gi://AstalWp";
const runnerPlugins: Array<Runner.Plugin> = [ const runnerPlugins: Array<Runner.Plugin> = [
@@ -284,11 +285,24 @@ you should use the socket in the XDG_RUNTIME_DIR/colorshell.sock for a faster re
console.log("Adding runner plugins"); console.log("Adding runner plugins");
runnerPlugins.forEach(plugin => Runner.addPlugin(plugin)); runnerPlugins.forEach(plugin => Runner.addPlugin(plugin));
this.#connections.set(Wireplumber.getDefault(), createSubscription(
Wireplumber.getDefault().getDefaultSink().connect("notify::volume", () => secureBaseBinding<AstalWp.Endpoint>(
!Windows.getDefault().isOpen("control-center") && createBinding(AstalWp.get_default(), "defaultSpeaker"),
triggerOSD(OSDModes.sink) "volume",
) null
),
() => !Windows.getDefault().isOpen("control-center") &&
triggerOSD(OSDModes.sink)
);
createSubscription(
secureBaseBinding<AstalWp.Endpoint>(
createBinding(AstalWp.get_default(), "defaultSpeaker"),
"mute",
null
),
() => !Windows.getDefault().isOpen("control-center") &&
triggerOSD(OSDModes.sink)
); );
createSubscription( createSubscription(
+160
View File
@@ -0,0 +1,160 @@
import { Scope } from "ags";
import { createScopedConnection, encoder } from "../modules/utils";
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";
export namespace Cli {
let rootScope: Scope;
let service: Gio.SocketService;
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"
};
}
}
],
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 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<string>, 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, socketService: Gio.SocketService): void {
if(initialized) return;
initialized = true;
rootScope = scope;
service = socketService;
DEVEL && modules.push(devel);
scope.run(() => {
createScopedConnection(
service, "incoming", (conn) => {
try {
return handleIncoming(conn);
} catch(_) {}
return false;
}
);
});
}
/** 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.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 */
function handleArgs(args: Array<string>): void {
let mod: Module;
}
}
+16
View File
@@ -0,0 +1,16 @@
import { Gtk } from "ags/gtk4";
import { Cli } from "..";
export default {
prefix: "dev",
help: "development tools to help debugging colorshell",
commands: [{
name: "inspector",
help: "open the gtk's visual inspector",
onCalled: () => {
Gtk.Window.set_interactive_debugging(true);
return "Opening GTK Inspector..."
}
}]
} satisfies Cli.Module;
+46
View File
@@ -0,0 +1,46 @@
import { Cli } from "..";
export default {
prefix: "media",
help: "manage colorshell's active player",
commands: [
{
name: "play",
help: "resume/start active player's media",
onCalled: () => "TODO"
}, {
name: "pause",
help: "pause the active player",
onCalled: () => "TODO"
}, {
name: "play-pause",
help: "toggle pause/resume the active player",
onCalled: () => "TODO"
}, {
name: "stop",
help: "stop the active player (if compatible)",
onCalled: () => "TODO"
}, {
name: "previous",
help: "go back to previous media in the active player",
onCalled: () => "TODO"
}, {
name: "next",
help: "jump to the next media in active player",
onCalled: () => "TODO"
}, {
name: "bus-name",
help: "retrieve the active player's mpris bus name",
onCalled: () => "TODO"
}, {
name: "list",
help: "list available players implementing mpris",
onCalled: () => "TODO"
}, {
name: "select",
help: "resume/start active player's media",
onCalled: (_, busName) => "TODO"
}
]
} satisfies Cli.Module;
+27
View File
@@ -0,0 +1,27 @@
import { Cli } from "..";
export default {
prefix: "volume",
help: "manage audio device volume/sensitivity. available devices are sink(speaker) and source(microphone).\
example usage: `colorshell volume increase sink 5%`",
commands: [
{
name: "increase",
help: "increase volume/sensitivity of a sink/source",
onCalled: () => "TODO"
}, {
name: "decrease",
help: "decrease volume/sensitivity of a sink/source",
onCalled: () => "TODO"
}, {
name: "set",
help: "set the volume/sensitivity of a sink/source",
onCalled: () => "TODO"
}, {
name: "mute",
help: "toggle-mute a sink/source's audio",
onCalled: () => "TODO"
}
]
} satisfies Cli.Module;
+46
View File
@@ -0,0 +1,46 @@
import { Cli } from "..";
export default [
{
name: "open",
onCalled: (_, data) => {
return {
type: "out",
content: "TODO"
}
}
}, {
name: "toggle",
onCalled: (_, data) => {
return {
type: "out",
content: "TODO"
}
}
}, {
name: "close",
onCalled: (_, data) => {
return {
type: "out",
content: "TODO"
}
}
}, {
name: "windows",
onCalled: () => {
return {
type: "out",
content: "TODO"
}
}
}, {
name: "reopen",
onCalled: () => {
return {
type: "out",
content: "TODO"
}
}
}
] satisfies Array<Cli.Command>;
+13 -17
View File
@@ -2,29 +2,29 @@ import { exec, execAsync } from "ags/process";
import { register } from "ags/gobject"; import { register } from "ags/gobject";
import { AuthPopup } from "../widget/AuthPopup"; import { AuthPopup } from "../widget/AuthPopup";
import AstalAuth from "gi://AstalAuth"; import Polkit from "gi://Polkit?version=1.0";
import Polkit from "gi://Polkit"; import PolkitAgent from "gi://PolkitAgent?version=1.0";
import PolkitAgent from "gi://PolkitAgent";
import Gio from "gi://Gio?version=2.0"; import Gio from "gi://Gio?version=2.0";
import GLib from "gi://GLib?version=2.0"; import GLib from "gi://GLib?version=2.0";
import AstalAuth from "gi://AstalAuth?version=0.1";
@register({ GTypeName: "AuthAgent" }) @register({ GTypeName: "AuthAgent" })
export class Auth extends PolkitAgent.Listener { export class Auth extends PolkitAgent.Listener {
private static instance: Auth; private static instance: Auth;
#subject: Polkit.Subject;
#pam: AstalAuth.Pam;
#handle: any; #handle: any;
#user: Polkit.UnixUser;
#pam: AstalAuth.Pam;
constructor() { constructor() {
super(); super();
this.#subject = Polkit.UnixSession.new(""); this.#user = Polkit.UnixUser.new(Number.parseInt(exec("id -u"))) as Polkit.UnixUser;
this.#pam = new AstalAuth.Pam(); this.#pam = new AstalAuth.Pam;
this.#handle = this.register( this.register(
PolkitAgent.RegisterFlags.RUN_IN_THREAD, PolkitAgent.RegisterFlags.RUN_IN_THREAD,
this.#subject, Polkit.UnixSession.new(this.#user.get_uid().toString()),
"/io/github/retrozinndev/colorshell/PolicyKit/AuthAgent", "/io/github/retrozinndev/colorshell/AuthAgent",
null null
); );
} }
@@ -57,12 +57,8 @@ export class Auth extends PolkitAgent.Listener {
} }
initiate_authentication_finish(res: Gio.AsyncResult): boolean {
public static initAgent(): Auth {
if(!this.instance)
this.instance = new Auth();
return this.instance;
} }
// TODO: support fingerprint/facial auth // TODO: support fingerprint/facial auth
+1 -1
View File
@@ -17,7 +17,7 @@ export {
createScopedConnection, createScopedConnection,
createSecureBinding as secureBinding, createSecureBinding as secureBinding,
createSecureAccessorBinding as secureBaseBinding, createSecureAccessorBinding as secureBaseBinding,
} from "../utils"; } from "gnim-utils";
export const decoder = new TextDecoder("utf-8"), export const decoder = new TextDecoder("utf-8"),
+2 -7
View File
@@ -1,11 +1,8 @@
import GObject, { register } from "ags/gobject";
import AstalWp from "gi://AstalWp"; import AstalWp from "gi://AstalWp";
export { Wireplumber };
@register({ GTypeName: "Wireplumber" }) export class Wireplumber {
class Wireplumber extends GObject.Object { private static astalWireplumber: AstalWp.Wp|null = AstalWp.get_default();
private static astalWireplumber: (AstalWp.Wp|null) = AstalWp.get_default();
private static inst: Wireplumber; private static inst: Wireplumber;
private defaultSink: AstalWp.Endpoint = Wireplumber.astalWireplumber!.get_default_speaker()!; private defaultSink: AstalWp.Endpoint = Wireplumber.astalWireplumber!.get_default_speaker()!;
@@ -15,8 +12,6 @@ class Wireplumber extends GObject.Object {
private maxSourceVolume: number = 100; private maxSourceVolume: number = 100;
constructor() { constructor() {
super();
if(!Wireplumber.astalWireplumber) if(!Wireplumber.astalWireplumber)
throw new Error("Audio features will not work correctly! Please install wireplumber first", { throw new Error("Audio features will not work correctly! Please install wireplumber first", {
cause: "Wireplumber library not found" cause: "Wireplumber library not found"
Submodule src/utils deleted from bedadf7a22