Merge pull request #71 from Aylur/feature/client-process

Print `--run-js` return value, introduce `--run-promise` flag (fixes #61)
This commit is contained in:
Aylur
2023-09-03 00:38:49 +02:00
committed by GitHub
7 changed files with 270 additions and 108 deletions
+3
View File
@@ -3,6 +3,7 @@
<gresource prefix="/com/github/Aylur/ags">
<file>main.js</file>
<file>app.js</file>
<file>client.js</file>
<file>utils.js</file>
<file>widget.js</file>
@@ -35,6 +36,8 @@
<file>service/notifications.js</file>
<file>dbus/types.js</file>
<file>dbus/com.github.Aylur.ags.xml</file>
<file>dbus/com.github.Aylur.ags.client.xml</file>
<file>dbus/org.freedesktop.DBus.xml</file>
<file>dbus/org.freedesktop.Notifications.xml</file>
<file>dbus/org.freedesktop.UPower.Device.xml</file>
+63 -32
View File
@@ -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<string, Gtk.Window>;
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(); }
}
+98
View File
@@ -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`);
}
+8
View File
@@ -0,0 +1,8 @@
<node>
<interface name="@BUS@">
<method name="Print">
<arg direction="in" type="s" name="string"/>
<arg direction="out" type="s" name="string"/>
</method>
</interface>
</node>
+19
View File
@@ -0,0 +1,19 @@
<node>
<interface name="@BUS@">
<method name='Inspector'/>
<method name='Quit'/>
<method name="ToggleWindow">
<arg direction="in" type="s" name="name"/>
<arg direction="out" type="s" name="state"/>
</method>
<method name="RunJs">
<arg direction="in" type="s" name="js"/>
<arg direction="out" type="s" name="output"/>
</method>
<method name="RunPromise">
<arg direction="in" type="s" name="js"/>
<arg direction="in" type="s" name="client-bus"/>
<arg direction="in" type="s" name="client-path"/>
</method>
</interface>
</node>
+17 -17
View File
@@ -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<void>
}
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
}
+62 -59
View File
@@ -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);
}
}