✨ feat: add support for unix socket communication
This commit is contained in:
+83
-10
@@ -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<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; }
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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<string>): number {
|
||||
export function handleArguments(cmd: RemoteCaller, args: Array<string>): number {
|
||||
switch(args[0]) {
|
||||
case "help":
|
||||
case "h":
|
||||
@@ -111,7 +115,7 @@ export function handleArguments(cmd: Gio.ApplicationCommandLine, args: Array<str
|
||||
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])) {
|
||||
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<string>): number {
|
||||
function handleWindowArgs(cmd: RemoteCaller, args: Array<string>): number {
|
||||
switch(args[0]) {
|
||||
case "reopen":
|
||||
Windows.getDefault().reopen();
|
||||
@@ -294,7 +298,7 @@ function handleWindowArgs(cmd: Gio.ApplicationCommandLine, args: Array<string>):
|
||||
return 1;
|
||||
}
|
||||
|
||||
function handleVolumeArgs(cmd: Gio.ApplicationCommandLine, args: Array<string>): number {
|
||||
function handleVolumeArgs(cmd: RemoteCaller, args: Array<string>): number {
|
||||
if(!args[1]) {
|
||||
cmd.printerr_literal(`Error: please specify what to do! see \`volume help\``);
|
||||
return 1;
|
||||
|
||||
Reference in New Issue
Block a user