feat: add support for unix socket communication

This commit is contained in:
retrozinndev
2025-08-14 19:06:58 -03:00
parent 4384f21979
commit c7a6ec0222
2 changed files with 92 additions and 15 deletions
+83 -10
View File
@@ -26,6 +26,8 @@ import { triggerOSD } from "./window/OSD";
import { programArgs, programInvocationName } from "system"; import { programArgs, programInvocationName } from "system";
import { setConsoleLogDomain } from "console"; import { setConsoleLogDomain } from "console";
import { initPlayer } from "./modules/media"; import { initPlayer } from "./modules/media";
import { encoder } from "./modules/utils";
import { exec } from "ags/process";
import GObject, { register } from "ags/gobject"; import GObject, { register } from "ags/gobject";
import AstalNotifd from "gi://AstalNotifd"; import AstalNotifd from "gi://AstalNotifd";
@@ -49,7 +51,7 @@ Gtk.init();
Adw.init(); Adw.init();
GLib.unsetenv("LD_PRELOAD"); GLib.unsetenv("LD_PRELOAD");
@register({ GTypeName: "Shell", Implements: [Gio.ActionGroup]}) @register({ GTypeName: "Shell" })
export class Shell extends Adw.Application implements Gio.ActionMap { export class Shell extends Adw.Application implements Gio.ActionMap {
private static instance: Shell; private static instance: Shell;
@@ -57,6 +59,8 @@ export class Shell extends Adw.Application implements Gio.ActionMap {
#connections = new Map<GObject.Object, Array<number> | number>(); #connections = new Map<GObject.Object, Array<number> | number>();
#providers: Array<Gtk.CssProvider> = []; #providers: Array<Gtk.CssProvider> = [];
#gresource: Gio.Resource|null = null; #gresource: Gio.Resource|null = null;
#socketService: Gio.SocketService;
#socketFile: Gio.File;
get scope() { return this.#scope; } get scope() { return this.#scope; }
@@ -69,11 +73,12 @@ export class Shell extends Adw.Application implements Gio.ActionMap {
setConsoleLogDomain("colorshell"); setConsoleLogDomain("colorshell");
// load gresource from build-defined path
try { try {
// load gresource from build-defined value + support env variables
this.#gresource = Gio.Resource.load(GRESOURCES_FILE.split('/').filter(s => this.#gresource = Gio.Resource.load(GRESOURCES_FILE.split('/').filter(s =>
s !== "" s !== ""
).map(path => { ).map(path => {
// support environment variables at runtime
if(/^\$/.test(path)) { if(/^\$/.test(path)) {
const env = GLib.getenv(path.replace(/^\$/, "")); const env = GLib.getenv(path.replace(/^\$/, ""));
if(env === null) if(env === null)
@@ -94,11 +99,75 @@ export class Shell extends Adw.Application implements Gio.ActionMap {
console.error(`Error: couldn't load gresource! Stderr: ${e.message}\n${e.stack}`); console.error(`Error: couldn't load gresource! Stderr: ${e.message}\n${e.stack}`);
} }
// create action for gapplication to handle commands via dbus this.#socketFile = Gio.File.new_for_path(`${GLib.get_user_runtime_dir() ??
// (faster than running a remote instance to send arguments) `/run/user/${exec("id -u").trim()}`}/colorshell.sock`);
// TODO: implement support for argument parsing through dbus
const msgAction = Gio.SimpleAction.new("msg", null); if(this.#socketFile.query_exists(null)) {
this.add_action(msgAction); 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
this.#connections.set(this.#socketService,
this.#socketService.connect("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),
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),
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;
})
);
} }
public static getDefault(): Shell { public static getDefault(): Shell {
@@ -154,12 +223,16 @@ export class Shell extends Adw.Application implements Gio.ActionMap {
} }
vfunc_command_line(cmd: Gio.ApplicationCommandLine): number { vfunc_command_line(cmd: Gio.ApplicationCommandLine): number {
const args = cmd.get_arguments(); const args = cmd.get_arguments().toSpliced(0, 1); // remove executable
args.splice(0, 1); // remove executable
if(cmd.isRemote) { if(cmd.isRemote) {
try { 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); const res = handleArguments(cmd, args);
cmd.done(); cmd.done();
cmd.set_exit_status(res); cmd.set_exit_status(res);
return res; return res;
@@ -197,7 +270,7 @@ export class Shell extends Adw.Application implements Gio.ActionMap {
initPlayer(); initPlayer();
Clipboard.getDefault(); Clipboard.getDefault();
console.log("Colorshell: Initializing wallpaper & Stylesheet handlers"); console.log("Colorshell: Initializing Wallpaper and Stylesheet modules");
Wallpaper.getDefault(); Wallpaper.getDefault();
Stylesheet.getDefault(); Stylesheet.getDefault();
+9 -5
View File
@@ -10,9 +10,13 @@ import { generalConfig, Shell } from "../app";
import AstalIO from "gi://AstalIO"; import AstalIO from "gi://AstalIO";
import AstalMpris from "gi://AstalMpris"; import AstalMpris from "gi://AstalMpris";
import Gio from "gi://Gio?version=2.0";
export type RemoteCaller = {
printerr_literal: (message: string) => void,
print_literal: (message: string) => void
};
let wsTimeout: AstalIO.Time|undefined; let wsTimeout: AstalIO.Time|undefined;
const help = `Manage Astal Windows and do more stuff. From retrozinndev's colorshell, \ const help = `Manage Astal Windows and do more stuff. From retrozinndev's colorshell, \
made using GTK4, AGS, Gnim and Astal libraries by Aylur. made using GTK4, AGS, Gnim and Astal libraries by Aylur.
@@ -42,7 +46,7 @@ made using GTK4, AGS, Gnim and Astal libraries by Aylur.
https://github.com/retrozinndev/colorshell https://github.com/retrozinndev/colorshell
`.split('\n').map(l => l.replace(/^ {8}/, "")).join('\n'); `.split('\n').map(l => l.replace(/^ {8}/, "")).join('\n');
export function handleArguments(cmd: Gio.ApplicationCommandLine, args: Array<string>): number { export function handleArguments(cmd: RemoteCaller, args: Array<string>): number {
switch(args[0]) { switch(args[0]) {
case "help": case "help":
case "h": case "h":
@@ -111,7 +115,7 @@ export function handleArguments(cmd: Gio.ApplicationCommandLine, args: Array<str
return 1; return 1;
} }
function handleMediaArgs(cmd: Gio.ApplicationCommandLine, args: Array<string>): number { function handleMediaArgs(cmd: RemoteCaller, args: Array<string>): number {
if(/h|help/.test(args[1])) { if(/h|help/.test(args[1])) {
const mediaHelp = ` const mediaHelp = `
Manage colorshell's active player Manage colorshell's active player
@@ -225,7 +229,7 @@ specified bus name does not exist/is not available!`);
return 1; return 1;
} }
function handleWindowArgs(cmd: Gio.ApplicationCommandLine, args: Array<string>): number { function handleWindowArgs(cmd: RemoteCaller, args: Array<string>): number {
switch(args[0]) { switch(args[0]) {
case "reopen": case "reopen":
Windows.getDefault().reopen(); Windows.getDefault().reopen();
@@ -294,7 +298,7 @@ function handleWindowArgs(cmd: Gio.ApplicationCommandLine, args: Array<string>):
return 1; return 1;
} }
function handleVolumeArgs(cmd: Gio.ApplicationCommandLine, args: Array<string>): number { function handleVolumeArgs(cmd: RemoteCaller, args: Array<string>): number {
if(!args[1]) { if(!args[1]) {
cmd.printerr_literal(`Error: please specify what to do! see \`volume help\``); cmd.printerr_literal(`Error: please specify what to do! see \`volume help\``);
return 1; return 1;