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:
Profpatsch 2024-12-10 03:16:33 +01:00
parent 4eeac3cb1d
commit c12fdeb8e0

View file

@ -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