From c7a6ec0222e7dae18c22ed6e356479ddf901ec23 Mon Sep 17 00:00:00 2001 From: retrozinndev Date: Thu, 14 Aug 2025 19:06:58 -0300 Subject: [PATCH] :sparkles: feat: add support for unix socket communication --- src/app.ts | 93 ++++++++++++++++++++++++++++++++++---- src/modules/arg-handler.ts | 14 ++++-- 2 files changed, 92 insertions(+), 15 deletions(-) diff --git a/src/app.ts b/src/app.ts index 333463c..3c04a35 100644 --- a/src/app.ts +++ b/src/app.ts @@ -26,6 +26,8 @@ import { triggerOSD } from "./window/OSD"; import { programArgs, programInvocationName } from "system"; import { setConsoleLogDomain } from "console"; import { initPlayer } from "./modules/media"; +import { encoder } from "./modules/utils"; +import { exec } from "ags/process"; import GObject, { register } from "ags/gobject"; import AstalNotifd from "gi://AstalNotifd"; @@ -49,7 +51,7 @@ Gtk.init(); Adw.init(); GLib.unsetenv("LD_PRELOAD"); -@register({ GTypeName: "Shell", Implements: [Gio.ActionGroup]}) +@register({ GTypeName: "Shell" }) export class Shell extends Adw.Application implements Gio.ActionMap { private static instance: Shell; @@ -57,6 +59,8 @@ export class Shell extends Adw.Application implements Gio.ActionMap { #connections = new Map | number>(); #providers: Array = []; #gresource: Gio.Resource|null = null; + #socketService: Gio.SocketService; + #socketFile: Gio.File; get scope() { return this.#scope; } @@ -69,11 +73,12 @@ export class Shell extends Adw.Application implements Gio.ActionMap { setConsoleLogDomain("colorshell"); + // load gresource from build-defined path try { - // load gresource from build-defined value + support env variables 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) @@ -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}`); } - // create action for gapplication to handle commands via dbus - // (faster than running a remote instance to send arguments) - // TODO: implement support for argument parsing through dbus - const msgAction = Gio.SimpleAction.new("msg", null); - this.add_action(msgAction); + 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 + 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 { @@ -154,12 +223,16 @@ export class Shell extends Adw.Application implements Gio.ActionMap { } vfunc_command_line(cmd: Gio.ApplicationCommandLine): number { - const args = cmd.get_arguments(); - args.splice(0, 1); // remove executable + 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; @@ -197,7 +270,7 @@ export class Shell extends Adw.Application implements Gio.ActionMap { initPlayer(); Clipboard.getDefault(); - console.log("Colorshell: Initializing wallpaper & Stylesheet handlers"); + console.log("Colorshell: Initializing Wallpaper and Stylesheet modules"); Wallpaper.getDefault(); Stylesheet.getDefault(); diff --git a/src/modules/arg-handler.ts b/src/modules/arg-handler.ts index d73e359..90b73a5 100644 --- a/src/modules/arg-handler.ts +++ b/src/modules/arg-handler.ts @@ -10,9 +10,13 @@ import { generalConfig, Shell } from "../app"; import AstalIO from "gi://AstalIO"; 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; const help = `Manage Astal Windows and do more stuff. From retrozinndev's colorshell, \ 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 `.split('\n').map(l => l.replace(/^ {8}/, "")).join('\n'); -export function handleArguments(cmd: Gio.ApplicationCommandLine, args: Array): number { +export function handleArguments(cmd: RemoteCaller, args: Array): number { switch(args[0]) { case "help": case "h": @@ -111,7 +115,7 @@ export function handleArguments(cmd: Gio.ApplicationCommandLine, args: Array): number { +function handleMediaArgs(cmd: RemoteCaller, args: Array): number { if(/h|help/.test(args[1])) { const mediaHelp = ` Manage colorshell's active player @@ -225,7 +229,7 @@ specified bus name does not exist/is not available!`); return 1; } -function handleWindowArgs(cmd: Gio.ApplicationCommandLine, args: Array): number { +function handleWindowArgs(cmd: RemoteCaller, args: Array): number { switch(args[0]) { case "reopen": Windows.getDefault().reopen(); @@ -294,7 +298,7 @@ function handleWindowArgs(cmd: Gio.ApplicationCommandLine, args: Array): return 1; } -function handleVolumeArgs(cmd: Gio.ApplicationCommandLine, args: Array): number { +function handleVolumeArgs(cmd: RemoteCaller, args: Array): number { if(!args[1]) { cmd.printerr_literal(`Error: please specify what to do! see \`volume help\``); return 1;