src/profile.js
Rama main · réplica de lectura del repositorio original.
// SPDX-License-Identifier: GPL-3.0-or-later
import { run, shq } from "./utils.js";
/**
* What kind of machine this is, so checks with an `appliesTo` list can be
* skipped when they are irrelevant (e.g. battery checks on a desktop).
*
* laptop — a real battery is present
* server — confirmed headless (loginctl exists but no graphical session)
* desktop — everything else (including non-systemd systems we cannot probe)
*
* Wireless-device batteries (Logitech receivers, game controllers) are
* excluded so they never turn a desktop into a "laptop".
*
* The default (no-injection) call is memoized: the profile cannot change while
* a process is running, and it costs a handful of subprocess spawns. Tests pass
* a custom `exec`, which always bypasses the cache.
*/
let cachedProfile = null;
/**
* Shell probe for the session id of a seat-backed (graphical) session.
* Exported so wayland.js reuses it and so tests can run the real parsing
* against fixtures. Never index a fixed column: `loginctl list-sessions`
* has gained columns across systemd versions, and `$2=="seat0"` used to
* read the UID — classifying every battery-less desktop as a headless
* server and skipping all desktop checks.
*/
export const SESSION_PROBE = 'loginctl list-sessions --no-legend 2>/dev/null | grep -m1 -E "(^|[[:space:]])seat[0-9]+([[:space:]]|$)" | cut -d" " -f1';
/** Drop the memoized profile — mainly for tests that inject a real exec. */
export function resetProfileCache() {
cachedProfile = null;
}
export async function detectProfile(exec = run) {
if (exec === run) {
cachedProfile ||= computeProfile(run);
return cachedProfile;
}
return computeProfile(exec);
}
async function computeProfile(exec) {
const supplies = (await exec("ls /sys/class/power_supply/ 2>/dev/null")).stdout.split(/\s+/).filter(Boolean);
const realSupplies = supplies.filter((s) => !/^(AC|ADP)/i.test(s) && !/hidpp|controller/i.test(s));
const types = await Promise.all(
realSupplies.map(async (s) => ({ s, type: await exec(`cat ${shq(`/sys/class/power_supply/${s}/type`)} 2>/dev/null`) }))
);
const hasBattery = types.some(({ type }) => type.ok && type.stdout.trim() === "Battery");
// Find a session whose SEAT is a seatN token (see SESSION_PROBE).
const session = await exec(SESSION_PROBE);
const hasSession = session.ok && session.stdout.trim() !== "";
let kind = "desktop";
if (hasBattery) kind = "laptop";
else if (session.ok && !hasSession) kind = "server"; // loginctl present, confirmed headless
return { kind, hasBattery, hasSession };
}