mirror of
https://github.com/zoriya/ags.git
synced 2026-06-07 20:21:39 +00:00
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:
@@ -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
@@ -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(); }
|
||||
}
|
||||
|
||||
@@ -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`);
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user