diff --git a/src/ags.src.gresource.xml b/src/ags.src.gresource.xml index 7194f51..4e16607 100644 --- a/src/ags.src.gresource.xml +++ b/src/ags.src.gresource.xml @@ -3,6 +3,7 @@ main.js app.js + client.js utils.js widget.js @@ -35,6 +36,8 @@ service/notifications.js dbus/types.js + dbus/com.github.Aylur.ags.xml + dbus/com.github.Aylur.ags.client.xml dbus/org.freedesktop.DBus.xml dbus/org.freedesktop.Notifications.xml dbus/org.freedesktop.UPower.Device.xml diff --git a/src/app.ts b/src/app.ts index 2e3500c..7ba1062 100644 --- a/src/app.ts +++ b/src/app.ts @@ -4,6 +4,10 @@ import Gio from 'gi://Gio'; import GLib from 'gi://GLib'; import GObject from 'gi://GObject'; import { timeout, connect } from './utils.js'; +import { loadInterfaceXML } from './utils.js'; + +const AgsIFace = (bus: string) => + loadInterfaceXML('com.github.Aylur.ags')?.replace('@BUS@', bus); interface Config { windows?: Gtk.Window[] @@ -24,22 +28,24 @@ export default class App extends Gtk.Application { }, this); } + private _dbus!: Gio.DBusExportedObject; private _windows: Map; private _closeDelay!: { [key: string]: number }; private _cssProviders: Gtk.CssProvider[] = []; + private _busName: string; + private _objectPath: string; static configPath: string; static configDir: string; static config: Config; static instance: App; - // eslint-disable-next-line max-len static removeWindow(w: Gtk.Window | string) { App.instance.removeWindow(w); } static addWindow(w: Gtk.Window) { App.instance.addWindow(w); } static get windows() { return App.instance._windows; } static getWindow(name: string) { return App.instance.getWindow(name); } static closeWindow(name: string) { App.instance.closeWindow(name); } - static openWindow(name: string) { App.getWindow(name)?.show(); } + static openWindow(name: string) { App.instance.openWindow(name); } static toggleWindow(name: string) { App.instance.toggleWindow(name); } static quit() { App.instance.quit(); } @@ -76,21 +82,20 @@ export default class App extends Gtk.Application { App.instance._cssProviders.push(cssProvider); } - constructor({ bus, config }: { - bus: string - config: string - }) { + constructor(bus: string, path: string, configPath: string) { super({ application_id: bus, flags: Gio.ApplicationFlags.DEFAULT_FLAGS, }); + this._busName = bus; + this._objectPath = path; this._windows = new Map(); - const dir = config.split('/'); + const dir = configPath.split('/'); dir.pop(); App.configDir = dir.join('/'); - App.configPath = config; + App.configPath = configPath; App.instance = this; } @@ -105,14 +110,20 @@ export default class App extends Gtk.Application { vfunc_activate() { this.hold(); + this._register(); this._load(); - this._exportActions(); } toggleWindow(name: string) { const w = this.getWindow(name); if (w) - w.visible ? App.closeWindow(name) : App.openWindow(name); + w.visible ? this.closeWindow(name) : this.openWindow(name); + else + return 'There is no window named ' + name; + } + + openWindow(name: string) { + this.getWindow(name)?.show(); } closeWindow(name: string) { @@ -202,29 +213,49 @@ export default class App extends Gtk.Application { } } - private _addAction( - name: string, - callback: (_source: Gio.SimpleAction, _param: GLib.Variant) => void, - parameter_type?: GLib.VariantType, - ) { - const action = parameter_type - ? new Gio.SimpleAction({ name, parameter_type }) - : new Gio.SimpleAction({ name }); - action.connect('activate', callback); - this.add_action(action); + private _register() { + Gio.bus_own_name( + Gio.BusType.SESSION, + this._busName, + Gio.BusNameOwnerFlags.NONE, + (connection: Gio.DBusConnection) => { + this._dbus = Gio.DBusExportedObject + .wrapJSObject(AgsIFace(this._busName) as string, this); + + this._dbus.export(connection, this._objectPath); + }, + null, + null, + ); } - private _exportActions() { - this._addAction('inspector', - () => Gtk.Window.set_interactive_debugging(true)); - this._addAction('quit', App.quit); - - this._addAction('toggle-window', (_, arg) => App.toggleWindow( - arg.unpack() as string), new GLib.VariantType('s')); - - this._addAction('run-js', (_, arg) => { - const fn = new Function(arg.unpack() as string); - fn(); - }, new GLib.VariantType('s')); + RunJs(js: string): string { + return js.includes(';') + ? `${Function(js)()}` + : `${Function('return ' + js)()}`; } + + RunPromise(js: string, busName?: string, objPath?: string) { + new Promise((res, rej) => Function('resolve', 'reject', js)(res, rej)) + .then(out => { + if (busName && objPath) { + Gio.DBus.session.call( + busName, objPath, busName, 'Print', + new GLib.Variant('(s)', [`${out}`]), + null, Gio.DBusCallFlags.NONE, -1, null, null, + ); + } + else { print(`${out}`); } + }) + .catch(logError); + } + + ToggleWindow(name: string) { + this.toggleWindow(name); + return `${this.getWindow(name)?.visible}`; + } + + Inspector() { Gtk.Window.set_interactive_debugging(true); } + + Quit() { this.quit(); } } diff --git a/src/client.ts b/src/client.ts new file mode 100644 index 0000000..f655863 --- /dev/null +++ b/src/client.ts @@ -0,0 +1,98 @@ +import Gtk from 'gi://Gtk?version=3.0'; +import GObject from 'gi://GObject'; +import Gio from 'gi://Gio'; +import GLib from 'gi://GLib'; +import { loadInterfaceXML } from './utils.js'; +import { type AgsProxy } from './dbus/types.js'; + +const AgsIFace = (bus: string) => + loadInterfaceXML('com.github.Aylur.ags')?.replace('@BUS@', bus); + +const ClientIFace = (bus: string) => + loadInterfaceXML('com.github.Aylur.ags.client')?.replace('@BUS@', bus); + +const TIME = `${GLib.DateTime.new_now_local().to_unix()}`; + +interface Flags { + busName: string + inspector: boolean + runJs: string + runPromise: string + toggleWindow: string + quit: boolean +} + +class Client extends Gtk.Application { + static { GObject.registerClass(this); } + + private _objectPath: string; + private _dbus!: Gio.DBusExportedObject; + private _proxy: AgsProxy; + private _promiseJs: string; + + constructor(bus: string, path: string, proxy: AgsProxy, js: string) { + super({ + application_id: bus + '.client' + TIME, + flags: Gio.ApplicationFlags.DEFAULT_FLAGS, + }); + + this._objectPath = path + '/client' + TIME; + this._proxy = proxy; + this._promiseJs = js; + } + + private _register() { + Gio.bus_own_name( + Gio.BusType.SESSION, + this.applicationId, + Gio.BusNameOwnerFlags.NONE, + (connection: Gio.DBusConnection) => { + this._dbus = Gio.DBusExportedObject + .wrapJSObject(ClientIFace(this.applicationId) as string, this); + + this._dbus.export(connection, this._objectPath); + }, + null, + null, + ); + } + + Print(str: string) { + print(str); + this.quit(); + return str; + } + + vfunc_activate(): void { + this.hold(); + this._register(); + this._proxy.RunPromiseRemote( + this._promiseJs, + this.applicationId, + this._objectPath, + ); + } +} + +export default function(bus: string, path: string, flags: Flags) { + const AgsProxy = Gio.DBusProxy.makeProxyWrapper(AgsIFace(bus)); + const proxy = new AgsProxy(Gio.DBus.session, bus, path) as AgsProxy; + + if (flags.toggleWindow) + print(proxy.ToggleWindowSync(flags.toggleWindow)); + + else if (flags.runJs) + print(proxy.RunJsSync(flags.runJs)); + + else if (flags.inspector) + proxy.InspectorRemote(); + + else if (flags.quit) + proxy.QuitRemote(); + + else if (flags.runPromise) + return new Client(bus, path, proxy, flags.runPromise).run(null); + + else + print(`Ags with busname "${flags.busName}" is already running`); +} diff --git a/src/dbus/com.github.Aylur.ags.client.xml b/src/dbus/com.github.Aylur.ags.client.xml new file mode 100644 index 0000000..9373dda --- /dev/null +++ b/src/dbus/com.github.Aylur.ags.client.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/dbus/com.github.Aylur.ags.xml b/src/dbus/com.github.Aylur.ags.xml new file mode 100644 index 0000000..552bcb2 --- /dev/null +++ b/src/dbus/com.github.Aylur.ags.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/dbus/types.ts b/src/dbus/types.ts index fa94d30..c1adad0 100644 --- a/src/dbus/types.ts +++ b/src/dbus/types.ts @@ -1,25 +1,13 @@ /* eslint-disable @typescript-eslint/no-misused-new */ import GLib from 'gi://GLib'; +import Gio from 'gi://Gio'; -interface Proxy { - connect: (event: string, callback: () => void) => number - disconnect: (id: number) => void - g_name_owner: string -} - -export interface DBusProxy extends Proxy { +export interface DBusProxy extends Gio.DBusProxy { new(...args: unknown[]): DBusProxy ListNamesRemote: (callback: (names: string[][]) => void) => void - connectSignal: ( - event: string, - callback: ( - proxy: string, - sender: string, - owners: string[] - ) => void) => void } -export interface PlayerProxy extends Proxy { +export interface PlayerProxy extends Gio.DBusProxy { new(...args: unknown[]): PlayerProxy CanControl: boolean CanGoNext: boolean @@ -40,7 +28,7 @@ export interface PlayerProxy extends Proxy { PlayAsync: () => Promise } -export interface MprisProxy extends Proxy { +export interface MprisProxy extends Gio.DBusProxy { new(...args: unknown[]): MprisProxy Raise: () => void Quit: () => void @@ -50,9 +38,21 @@ export interface MprisProxy extends Proxy { DesktopEntry: string } -export interface BatteryProxy extends Proxy { +export interface BatteryProxy extends Gio.DBusProxy { new(...args: unknown[]): BatteryProxy State: number Percentage: number IsPresent: boolean } + +export interface AgsProxy extends Gio.DBusProxy { + new(...args: unknown[]): AgsProxy + InspectorRemote: () => void; + QuitRemote: () => void; + ToggleWindowSync: (name: string) => boolean; + RunJsSync: (js: string) => string; + RunPromiseRemote: ( + js: string, + busName?: string, + objPath?: string) => void +} diff --git a/src/main.ts b/src/main.ts index c33df03..c9d8664 100644 --- a/src/main.ts +++ b/src/main.ts @@ -2,6 +2,7 @@ import Gio from 'gi://Gio'; import GLib from 'gi://GLib'; import * as Utils from './utils.js'; import App from './app.js'; +import client from './client.js'; import Service from './service/service.js'; import Widget from './widget.js'; import './service/apps.js'; @@ -13,8 +14,8 @@ import './service/mpris.js'; import './service/network.js'; import './service/notifications.js'; -const APP_BUS = (name: string) => 'com.github.Aylur.' + name; -const APP_PATH = (name: string) => '/com/github/Aylur/' + name; +const APP_BUS = (name: string) => 'com.github.Aylur.ags.' + name; +const APP_PATH = (name: string) => '/com/github/Aylur/ags/' + name; const DEFAULT_CONF = `${GLib.get_user_config_dir()}/${pkg.name}/config.js`; const help = (bin: string) => `USAGE: @@ -22,49 +23,32 @@ const help = (bin: string) => `USAGE: OPTIONS: -h, --help Print this help and exit + -v, --version Print version and exit - -q, --quit Kills AGS + + -q, --quit Kill AGS + -c, --config Path to the config file. Default: ${DEFAULT_CONF} - -b, --bus-name Bus name of the process, - can be used to launch multiple instances - -i, --inspector Open up the Gtk debug tool, - useful for fetching css selectors + + -b, --bus-name Bus name of the process + + -i, --inspector Open up the Gtk debug tool + -t, --toggle-window Show or hide a window - -r, --run-js Evaluate given string as a function and execute it. - NOTE: It won't print anything, - but if the function logs something, - it can be seen on AGS's stdout. - --clear-cache Removes ${Utils.CACHE_DIR} + + -r, --run-js Evaluate given string as a function and execute it + + -p, --run-promise Evaluate and execute function as Promise + + --clear-cache Remove ${Utils.CACHE_DIR} EXAMPLES ags --config $HOME/.config/ags/main.js --bus-name second-instance - ags --run-js "ags.Service.Mpris.getPlayer()?.playPause()" - ags --toggle-window "window-name"`; - -function client( - busName: string, - inspector: boolean, - runJs: string, - toggleWindow: string, - quit: boolean, -) { - const actions = Gio.DBusActionGroup.get( - Gio.DBus.session, APP_BUS(busName), APP_PATH(busName)); - - if (toggleWindow) { - actions.activate_action('toggle-window', - new GLib.Variant('s', toggleWindow)); - } - - if (runJs) - actions.activate_action('run-js', new GLib.Variant('s', runJs)); - - if (inspector) - actions.activate_action('inspector', null); - - if (quit) - actions.activate_action('quit', null); -} + ags --run-js "ags.Service.Mpris.getPlayer()?.name" + ags --run-promise "ags.Utils.timeout(1000, () => { + resolve('a second later'); + })" + ags --toggle-window window-name`; function isRunning(dbusName: string) { return Gio.DBus.session.call_sync( @@ -82,12 +66,15 @@ function isRunning(dbusName: string) { } export function main(args: string[]) { - let appBus = pkg.name; - let config = DEFAULT_CONF; - let inspector = false; - let runJs = ''; - let toggleWindow = ''; - let quit = false; + const flags = { + busName: pkg.name, + config: DEFAULT_CONF, + inspector: false, + runJs: '', + runPromise: '', + toggleWindow: '', + quit: false, + }; for (let i = 1; i < args.length; ++i) { switch (args[i]) { @@ -110,36 +97,42 @@ export function main(args: string[]) { case '-b': case '--bus-name': - appBus = args[++i]; + flags.busName = args[++i]; break; case '-c': case '--config': - config = args[++i]; + flags.config = args[++i]; break; case 'inspector': case '-i': case '--inspector': - inspector = true; + flags.inspector = true; break; case 'run-js': case '-r': case '--run-js': - runJs = args[++i]; + flags.runJs = args[++i]; + break; + + case 'run-promise': + case '-p': + case '--run-promise': + flags.runPromise = args[++i]; break; case 'toggle-window': case '-t': case '--toggle-window': - toggleWindow = args[++i]; + flags.toggleWindow = args[++i]; break; case 'quit': case '-q': case '--quit': - quit = true; + flags.quit = true; break; default: @@ -156,19 +149,29 @@ export function main(args: string[]) { Service, }; - const bus = APP_BUS(appBus); + const bus = APP_BUS(flags.busName); + const path = APP_PATH(flags.busName); + if (!isRunning(bus)) { - const app = new App({ bus, config }); + const app = new App(bus, path, flags.config); app.connect('config-parsed', () => { - client(appBus, inspector, runJs, toggleWindow, quit); + if (flags.toggleWindow) + app.ToggleWindow(flags.toggleWindow); + + if (flags.runJs) + app.RunJs(flags.runJs); + + if (flags.runPromise) + app.RunPromise(flags.runPromise); + + if (flags.inspector) + app.Inspector(); }); // @ts-ignore return app.runAsync(null); } - - client(appBus, inspector, runJs, toggleWindow, quit); - - if (!inspector && !runJs && !toggleWindow && !quit) - print(`Ags with busname "${appBus}" is already running`); + else { + return client(bus, path, flags); + } }