refactor(users/Profpatsch): simple wrapper around the dbus lib
This makes defining the interface a little less verbose & more typesafe (checks are done by the dbus library). Change-Id: I16df987fd152cabf76ed9878ed1a372a0f7003fb Reviewed-on: https://cl.tvl.fyi/c/depot/+/12886 Reviewed-by: Profpatsch <mail@profpatsch.de> Tested-by: BuildkiteCI
This commit is contained in:
parent
4eeac3cb1d
commit
c12fdeb8e0
1 changed files with 161 additions and 89 deletions
|
@ -35,9 +35,113 @@ const lightTheme = getThemePathSync(lightThemeName);
|
||||||
console.log(`Dark theme: ${darkTheme}`);
|
console.log(`Dark theme: ${darkTheme}`);
|
||||||
console.log(`Light theme: ${lightTheme}`);
|
console.log(`Light theme: ${lightTheme}`);
|
||||||
|
|
||||||
|
class Bus {
|
||||||
|
/**
|
||||||
|
* @param {'session' | 'system'} type
|
||||||
|
* @param {string} name
|
||||||
|
*/
|
||||||
|
constructor(name, type) {
|
||||||
|
this._name = name;
|
||||||
|
this._type = type;
|
||||||
|
switch (type) {
|
||||||
|
case 'session':
|
||||||
|
this._bus = dbus.sessionBus();
|
||||||
|
break;
|
||||||
|
case 'system':
|
||||||
|
this._bus = dbus.systemBus();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
this._bus.connection.once('error', err => {
|
||||||
|
console.error(`${this._type} bus ${this._name} error: ${err}`);
|
||||||
|
throw new Error(`${this._type} bus ${this._name} error: ${err}`);
|
||||||
|
});
|
||||||
|
this._bus.connection.once('end', () => {
|
||||||
|
console.error(`${this._type} bus ${this._name} connection ended unexpectedly`);
|
||||||
|
throw new Error(`${this._type} bus ${this._name} connection ended unexpectedly`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_busErrorMessages(what) {
|
||||||
|
return `Error getting ${what} from ${this._type} bus ${this._name}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} name
|
||||||
|
* @param {number} flags
|
||||||
|
* @returns {Promise<number>}
|
||||||
|
*/
|
||||||
|
requestName(name, flags) {
|
||||||
|
return promisifyMethodAnnotate(
|
||||||
|
this._bus,
|
||||||
|
// @ts-ignore
|
||||||
|
this._bus.requestName,
|
||||||
|
this._busErrorMessages(`requesting name ${name}`),
|
||||||
|
name,
|
||||||
|
flags,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {{ [key: string]: unknown }} iface
|
||||||
|
* @param {string} path
|
||||||
|
* @param {{ name: string; methods: { [key: string]: unknown }; }} opts
|
||||||
|
*/
|
||||||
|
exportInterface(iface, path, opts) {
|
||||||
|
// @ts-ignore
|
||||||
|
return this._bus.exportInterface(iface, path, opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
/** Get object from bus, with the given interface (not checked!)
|
||||||
|
* @template {{[key: string]: (...args: any[]) => Promise<unknown>}} Interface
|
||||||
|
* @param {string} serviceName
|
||||||
|
* @param {string} interfaceName
|
||||||
|
* @param {string} objectName object name
|
||||||
|
* @returns {Promise<Interface & EventEmitter >}
|
||||||
|
*/
|
||||||
|
async getObject(serviceName, interfaceName, objectName) {
|
||||||
|
//@ts-ignore
|
||||||
|
const s = this._bus.getService(serviceName);
|
||||||
|
|
||||||
|
/** @type {{[key: string]: Function}} */
|
||||||
|
// @ts-ignore
|
||||||
|
const iface = await promisifyMethodAnnotate(
|
||||||
|
s,
|
||||||
|
s.getInterface,
|
||||||
|
this._busErrorMessages(`interface ${interfaceName}`),
|
||||||
|
objectName,
|
||||||
|
interfaceName,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!iface) {
|
||||||
|
throw new Error(
|
||||||
|
`Interface ${interfaceName} not found on object ${objectName} of service ${serviceName}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need to promisify all methods on the interface
|
||||||
|
const methodNames = Object.keys(iface.$methods ?? {});
|
||||||
|
const methods = {};
|
||||||
|
/** @type {Record<string, Function>} */
|
||||||
|
for (const methodName of methodNames) {
|
||||||
|
methods[methodName] = iface[methodName];
|
||||||
|
iface[methodName] = (...args) =>
|
||||||
|
promisifyMethodAnnotate(
|
||||||
|
iface,
|
||||||
|
methods[methodName],
|
||||||
|
this._busErrorMessages(`method ${methodName}`),
|
||||||
|
...args,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
return iface;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Connect to the user session bus
|
// Connect to the user session bus
|
||||||
/** @type any */
|
const bus = new Bus('color-scheme', 'session');
|
||||||
const bus = dbus.sessionBus();
|
|
||||||
|
|
||||||
opentelemetry.diag.setLogger({ ...console, verbose: console.log });
|
opentelemetry.diag.setLogger({ ...console, verbose: console.log });
|
||||||
|
|
||||||
|
@ -167,17 +271,6 @@ function setupActiveSpan(tracer, spanData) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// tracer.startActiveSpan('222', span => {
|
|
||||||
// span.setAttribute('blabla', 'alacritty-change-color-scheme');
|
|
||||||
// tracer.startActiveSpan('333', span => {
|
|
||||||
// span.setAttribute('foo', 'bar');
|
|
||||||
// span.end();
|
|
||||||
// });
|
|
||||||
// // span.setAttribute('service.name', 'alacritty-change-color-scheme');
|
|
||||||
// // span.setAttribute('service.version', '0.0.1');
|
|
||||||
// span.end();
|
|
||||||
// });
|
|
||||||
|
|
||||||
// set XDG_CONFIG_HOME if it's not set
|
// set XDG_CONFIG_HOME if it's not set
|
||||||
if (!process.env.XDG_CONFIG_HOME) {
|
if (!process.env.XDG_CONFIG_HOME) {
|
||||||
process.env.XDG_CONFIG_HOME = process.env.HOME + '/.config';
|
process.env.XDG_CONFIG_HOME = process.env.HOME + '/.config';
|
||||||
|
@ -196,7 +289,8 @@ function getThemePathSync(theme) {
|
||||||
*/
|
*/
|
||||||
function writeAlacrittyColorConfig(cs) {
|
function writeAlacrittyColorConfig(cs) {
|
||||||
const theme = cs === 'prefer-dark' ? darkTheme : lightTheme;
|
const theme = cs === 'prefer-dark' ? darkTheme : lightTheme;
|
||||||
console.log(`Writing color scheme ${cs} with theme ${theme}`);
|
console.log(`
|
||||||
|
Writing color scheme ${cs} with theme ${theme}`);
|
||||||
fs.writeFileSync(
|
fs.writeFileSync(
|
||||||
process.env.XDG_CONFIG_HOME + '/alacritty/alacritty-colors-autogen.toml',
|
process.env.XDG_CONFIG_HOME + '/alacritty/alacritty-colors-autogen.toml',
|
||||||
`# !! THIS FILE IS GENERATED BY ${programName}
|
`# !! THIS FILE IS GENERATED BY ${programName}
|
||||||
|
@ -205,27 +299,35 @@ general.import = ["${theme}"]`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Typescript type that returns the inner value type T from a Promise<T>
|
||||||
|
* type PromiseVal<T> = T extends Promise<infer U> ? U : T;
|
||||||
|
* @template T
|
||||||
|
* @typedef {T extends Promise<infer U> ? U : T } PromiseVal
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template {{[key: string]: (...args: any[]) => Promise<unknown>}} T
|
||||||
|
* @typedef {typeof Bus.prototype.getObject<T>} GetObject<T>
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template {{[key: string]: (...args: any[]) => Promise<unknown>}} T
|
||||||
|
* @typedef {PromiseVal<ReturnType<GetObject<T>>>} IfaceReturn<T> */
|
||||||
|
|
||||||
/** get the current value of the color scheme from dbus
|
/** get the current value of the color scheme from dbus
|
||||||
*
|
*
|
||||||
* @returns {Promise<'prefer-dark' | 'prefer-light'>}
|
* @returns {Promise<'prefer-dark' | 'prefer-light'>}
|
||||||
*/
|
*/
|
||||||
async function getColorScheme() {
|
async function getColorScheme() {
|
||||||
let service = bus.getService('org.freedesktop.portal.Desktop');
|
/** @typedef {{ReadOne: (interface: string, settingName: string) => Promise<[unknown, ['prefer-dark' | 'prefer-light']]>}} ColorScheme */
|
||||||
let iface = await promisifyMethodAnnotate(
|
/** @type {IfaceReturn<ColorScheme>} */
|
||||||
service,
|
let iface = await bus.getObject(
|
||||||
service.getInterface,
|
'org.freedesktop.portal.Desktop',
|
||||||
'Error getting interface',
|
|
||||||
'/org/freedesktop/portal/desktop',
|
|
||||||
'org.freedesktop.portal.Settings',
|
'org.freedesktop.portal.Settings',
|
||||||
|
'/org/freedesktop/portal/desktop',
|
||||||
);
|
);
|
||||||
|
|
||||||
const [_, [value]] = await promisifyMethodAnnotate(
|
const [_, [value]] = await iface.ReadOne('org.gnome.desktop.interface', 'color-scheme');
|
||||||
iface,
|
|
||||||
iface.ReadOne,
|
|
||||||
'Error reading color scheme',
|
|
||||||
'org.gnome.desktop.interface',
|
|
||||||
'color-scheme',
|
|
||||||
);
|
|
||||||
assert(value === 'prefer-dark' || value === 'prefer-light');
|
assert(value === 'prefer-dark' || value === 'prefer-light');
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
@ -260,14 +362,11 @@ function writeAlacrittyColorConfigIfDifferent(cs) {
|
||||||
|
|
||||||
/** Listen on the freedesktop SettingChanged dbus interface for the color-scheme setting to change. */
|
/** Listen on the freedesktop SettingChanged dbus interface for the color-scheme setting to change. */
|
||||||
async function listenForColorschemeChange() {
|
async function listenForColorschemeChange() {
|
||||||
const service = bus.getService('org.freedesktop.portal.Desktop');
|
/** @type {PromiseVal<ReturnType<typeof bus.getObject<{}>>>} */
|
||||||
|
const iface = await bus.getObject(
|
||||||
const iface = await promisifyMethodAnnotate(
|
'org.freedesktop.portal.Desktop',
|
||||||
service,
|
|
||||||
service.getInterface,
|
|
||||||
'Error getting interface',
|
|
||||||
'/org/freedesktop/portal/desktop',
|
|
||||||
'org.freedesktop.portal.Settings',
|
'org.freedesktop.portal.Settings',
|
||||||
|
'/org/freedesktop/portal/desktop',
|
||||||
);
|
);
|
||||||
|
|
||||||
// Listen for SettingChanged signals
|
// Listen for SettingChanged signals
|
||||||
|
@ -299,18 +398,12 @@ async function exportColorSchemeDbusInterface() {
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const retCode = await promisifyMethodAnnotate(
|
bus;
|
||||||
bus,
|
const retCode = await bus.requestName(ifaceName, 0);
|
||||||
bus.requestName,
|
|
||||||
'Error requesting name for interface de.profpatsch.alacritty.ColorScheme',
|
|
||||||
ifaceName,
|
|
||||||
0,
|
|
||||||
);
|
|
||||||
console.log(
|
console.log(
|
||||||
`Request name returned ${retCode} for interface de.profpatsch.alacritty.ColorScheme`,
|
`Request name returned ${retCode} for interface de.profpatsch.alacritty.ColorScheme`,
|
||||||
);
|
);
|
||||||
bus.exportInterface(ifaceImpl, '/de/profpatsch/alacritty/ColorScheme', iface);
|
bus.exportInterface(ifaceImpl, '/de/profpatsch/alacritty/ColorScheme', iface);
|
||||||
bus.exportInterface(ifaceImpl, '/de/profpatsch/alacritty/ColorScheme2', iface);
|
|
||||||
console.log('Exported interface de.profpatsch.alacritty.ColorScheme');
|
console.log('Exported interface de.profpatsch.alacritty.ColorScheme');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log('Error exporting interface de.profpatsch.alacritty.ColorScheme');
|
console.log('Error exporting interface de.profpatsch.alacritty.ColorScheme');
|
||||||
|
@ -330,20 +423,12 @@ function annotateErr(msg) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @type any */
|
const bus2 = new Bus('otel', 'session');
|
||||||
const bus2 = dbus.sessionBus();
|
|
||||||
async function exportOtelInterface() {
|
async function exportOtelInterface() {
|
||||||
console.log('Exporting OpenTelemetry interface');
|
console.log('Exporting OpenTelemetry interface');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const retCode = await promisifyMethodAnnotate(
|
const retCode = bus2.requestName('de.profpatsch.otel.Tracer', 0);
|
||||||
bus2,
|
|
||||||
bus2.requestName,
|
|
||||||
'Error requesting name for interface de.profpatsch.otel.Tracer',
|
|
||||||
|
|
||||||
'de.profpatsch.otel.Tracer',
|
|
||||||
0,
|
|
||||||
);
|
|
||||||
console.log(
|
console.log(
|
||||||
`Request name returned ${retCode} for interface de.profpatsch.otel.Tracer`,
|
`Request name returned ${retCode} for interface de.profpatsch.otel.Tracer`,
|
||||||
);
|
);
|
||||||
|
@ -442,49 +527,38 @@ async function getParentCallsite() {
|
||||||
async function setupTracer(tracerName) {
|
async function setupTracer(tracerName) {
|
||||||
const parentCallsite = await getParentCallsite();
|
const parentCallsite = await getParentCallsite();
|
||||||
console.log(`Setting up tracer ${tracerName} from ${parentCallsite?.getFileName()}`);
|
console.log(`Setting up tracer ${tracerName} from ${parentCallsite?.getFileName()}`);
|
||||||
const service = bus2.getService('de.profpatsch.otel.Tracer');
|
|
||||||
const iface = await promisifyMethodAnnotate(
|
/** @typedef {{CreateTracer: (name: string) => Promise<string>}} TracerFactory */
|
||||||
service,
|
/** @type {IfaceReturn<TracerFactory>} */
|
||||||
service.getInterface,
|
const iface = await bus2.getObject(
|
||||||
'Error getting interface',
|
|
||||||
'/de/profpatsch/otel/TracerFactory',
|
|
||||||
'de.profpatsch.otel.TracerFactory',
|
|
||||||
);
|
|
||||||
const path = await promisifyMethodAnnotate(
|
|
||||||
iface,
|
|
||||||
iface.CreateTracer,
|
|
||||||
'Error creating tracer',
|
|
||||||
tracerName,
|
|
||||||
);
|
|
||||||
const tracerIface = await promisifyMethodAnnotate(
|
|
||||||
service,
|
|
||||||
service.getInterface,
|
|
||||||
'Error getting interface',
|
|
||||||
path,
|
|
||||||
'de.profpatsch.otel.Tracer',
|
'de.profpatsch.otel.Tracer',
|
||||||
|
'de.profpatsch.otel.TracerFactory',
|
||||||
|
'/de/profpatsch/otel/TracerFactory',
|
||||||
|
);
|
||||||
|
const path = await iface.CreateTracer(tracerName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {{
|
||||||
|
* StartSpan: (spanData: string) => Promise<void>,
|
||||||
|
* EndSpan: (spanData: string) => Promise<void>
|
||||||
|
* BatchSpans: ([bool, string]) => Promise<void>
|
||||||
|
* }} Tracer
|
||||||
|
* @type {IfaceReturn<Tracer>}
|
||||||
|
* */
|
||||||
|
const tracerIface = await bus2.getObject(
|
||||||
|
'de.profpatsch.otel.Tracer',
|
||||||
|
'de.profpatsch.otel.Tracer',
|
||||||
|
path,
|
||||||
);
|
);
|
||||||
|
|
||||||
function StartSpan(spanData) {
|
function StartSpan(spanData) {
|
||||||
return promisifyMethodAnnotate(
|
return tracerIface.StartSpan(JSON.stringify(spanData));
|
||||||
tracerIface,
|
|
||||||
tracerIface.StartSpan,
|
|
||||||
'Error starting span',
|
|
||||||
JSON.stringify(spanData),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
function EndSpan(spanData) {
|
function EndSpan(spanData) {
|
||||||
return promisifyMethodAnnotate(
|
return tracerIface.EndSpan(JSON.stringify(spanData));
|
||||||
tracerIface,
|
|
||||||
tracerIface.EndSpan,
|
|
||||||
'Error ending span',
|
|
||||||
JSON.stringify(spanData),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
function BatchSpans(spans) {
|
function BatchSpans(spans) {
|
||||||
return promisifyMethodAnnotate(
|
return tracerIface.BatchSpans(
|
||||||
tracerIface,
|
|
||||||
tracerIface.BatchSpans,
|
|
||||||
'Error batching spans',
|
|
||||||
spans.map(([isStartSpan, span]) => [isStartSpan, JSON.stringify(span)]),
|
spans.map(([isStartSpan, span]) => [isStartSpan, JSON.stringify(span)]),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -548,8 +622,6 @@ async function main() {
|
||||||
[false, { spanId: 'batchy', endTime: t + 1000 }],
|
[false, { spanId: 'batchy', endTime: t + 1000 }],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// TODO: proper error handling, through proper callback promises for dbus function.
|
|
||||||
|
|
||||||
await exportColorSchemeDbusInterface();
|
await exportColorSchemeDbusInterface();
|
||||||
|
|
||||||
// get the current color scheme
|
// get the current color scheme
|
||||||
|
|
Loading…
Reference in a new issue