@gtkx/vitest 2.0.0-beta.3 → 2.0.0-beta.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/headless-config.d.ts +7 -0
- package/dist/headless-config.d.ts.map +1 -0
- package/dist/headless-config.js +44 -0
- package/dist/headless-config.js.map +1 -0
- package/dist/headless-display.d.ts.map +1 -1
- package/dist/headless-display.js +26 -33
- package/dist/headless-display.js.map +1 -1
- package/dist/headless.d.ts +1 -0
- package/dist/headless.d.ts.map +1 -1
- package/dist/headless.js +1 -0
- package/dist/headless.js.map +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/dist/reap-headless-displays.d.ts +10 -0
- package/dist/reap-headless-displays.d.ts.map +1 -0
- package/dist/reap-headless-displays.js +211 -0
- package/dist/reap-headless-displays.js.map +1 -0
- package/dist/worker-preload.js +2 -1
- package/dist/worker-preload.js.map +1 -1
- package/package.json +3 -3
- package/src/headless-config.ts +61 -0
- package/src/headless-display.ts +34 -38
- package/src/headless.ts +6 -0
- package/src/index.ts +8 -0
- package/src/reap-headless-displays.ts +309 -0
- package/src/worker-preload.ts +3 -1
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
declare const HEADLESS_RUNTIME_MARKER = ".gtkx-headless-runtime";
|
|
2
|
+
declare const createSwayConfig: (width: string, height: string) => string;
|
|
3
|
+
declare const createBusConfig: (busSocketPath: string) => string;
|
|
4
|
+
declare const createHeadlessRuntimeMarker: (runtimeDir: string) => string;
|
|
5
|
+
declare const isSwayConfig: (value: string) => boolean;
|
|
6
|
+
export { createBusConfig, createHeadlessRuntimeMarker, createSwayConfig, HEADLESS_RUNTIME_MARKER, isSwayConfig, };
|
|
7
|
+
//# sourceMappingURL=headless-config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"headless-config.d.ts","sourceRoot":"","sources":["../src/headless-config.ts"],"names":[],"mappings":"AAIA,QAAA,MAAM,uBAAuB,2BAA2B,CAAC;AAazD,QAAA,MAAM,gBAAgB,GAAI,OAAO,MAAM,EAAE,QAAQ,MAAM,KAAG,MAU1C,CAAC;AAEjB,QAAA,MAAM,eAAe,GAAI,eAAe,MAAM,KAAG,MAajC,CAAC;AAEjB,QAAA,MAAM,2BAA2B,GAAI,YAAY,MAAM,KAAG,MACc,CAAC;AAEzE,QAAA,MAAM,YAAY,GAAI,OAAO,MAAM,KAAG,OAKrC,CAAC;AAEF,OAAO,EACH,eAAe,EACf,2BAA2B,EAC3B,gBAAgB,EAChB,uBAAuB,EACvB,YAAY,GACf,CAAC"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
const BUS_CONFIG_DOCTYPE = '<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN" ' +
|
|
2
|
+
'"https://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">';
|
|
3
|
+
const HEADLESS_RUNTIME_MARKER = ".gtkx-headless-runtime";
|
|
4
|
+
const SWAY_CONFIG_LINES = [
|
|
5
|
+
/^xwayland disable$/,
|
|
6
|
+
/^default_border none$/,
|
|
7
|
+
/^default_floating_border none$/,
|
|
8
|
+
/^output HEADLESS-1 resolution [1-9]\d*x[1-9]\d*$/,
|
|
9
|
+
/^output HEADLESS-1 bg #000000 solid_color$/,
|
|
10
|
+
/^for_window \[app_id="\.\*"\] floating enable, border none$/,
|
|
11
|
+
/^for_window \[title="\.\*"\] floating enable, border none$/,
|
|
12
|
+
/^$/,
|
|
13
|
+
];
|
|
14
|
+
const createSwayConfig = (width, height) => [
|
|
15
|
+
"xwayland disable",
|
|
16
|
+
"default_border none",
|
|
17
|
+
"default_floating_border none",
|
|
18
|
+
`output HEADLESS-1 resolution ${width}x${height}`,
|
|
19
|
+
"output HEADLESS-1 bg #000000 solid_color",
|
|
20
|
+
'for_window [app_id=".*"] floating enable, border none',
|
|
21
|
+
'for_window [title=".*"] floating enable, border none',
|
|
22
|
+
"",
|
|
23
|
+
].join("\n");
|
|
24
|
+
const createBusConfig = (busSocketPath) => [
|
|
25
|
+
BUS_CONFIG_DOCTYPE,
|
|
26
|
+
"<busconfig>",
|
|
27
|
+
" <type>session</type>",
|
|
28
|
+
` <listen>unix:path=${busSocketPath}</listen>`,
|
|
29
|
+
" <auth>EXTERNAL</auth>",
|
|
30
|
+
' <policy context="default">',
|
|
31
|
+
' <allow send_destination="*" eavesdrop="true"/>',
|
|
32
|
+
' <allow eavesdrop="true"/>',
|
|
33
|
+
' <allow own="*"/>',
|
|
34
|
+
" </policy>",
|
|
35
|
+
"</busconfig>",
|
|
36
|
+
].join("\n");
|
|
37
|
+
const createHeadlessRuntimeMarker = (runtimeDir) => ["gtkx-headless-runtime-v1", `runtime=${runtimeDir}`, ""].join("\n");
|
|
38
|
+
const isSwayConfig = (value) => {
|
|
39
|
+
const lines = value.split("\n");
|
|
40
|
+
return lines.length === SWAY_CONFIG_LINES.length &&
|
|
41
|
+
SWAY_CONFIG_LINES.every((pattern, index) => pattern.test(lines[index] ?? "invalid"));
|
|
42
|
+
};
|
|
43
|
+
export { createBusConfig, createHeadlessRuntimeMarker, createSwayConfig, HEADLESS_RUNTIME_MARKER, isSwayConfig, };
|
|
44
|
+
//# sourceMappingURL=headless-config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"headless-config.js","sourceRoot":"","sources":["../src/headless-config.ts"],"names":[],"mappings":"AAAA,MAAM,kBAAkB,GACpB,mFAAmF;IACnF,iEAAiE,CAAC;AAEtE,MAAM,uBAAuB,GAAG,wBAAwB,CAAC;AAEzD,MAAM,iBAAiB,GAAG;IACtB,oBAAoB;IACpB,uBAAuB;IACvB,gCAAgC;IAChC,kDAAkD;IAClD,4CAA4C;IAC5C,6DAA6D;IAC7D,4DAA4D;IAC5D,IAAI;CACP,CAAC;AAEF,MAAM,gBAAgB,GAAG,CAAC,KAAa,EAAE,MAAc,EAAU,EAAE,CAC/D;IACI,kBAAkB;IAClB,qBAAqB;IACrB,8BAA8B;IAC9B,gCAAgC,KAAK,IAAI,MAAM,EAAE;IACjD,0CAA0C;IAC1C,uDAAuD;IACvD,sDAAsD;IACtD,EAAE;CACL,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAEjB,MAAM,eAAe,GAAG,CAAC,aAAqB,EAAU,EAAE,CACtD;IACI,kBAAkB;IAClB,aAAa;IACb,wBAAwB;IACxB,uBAAuB,aAAa,WAAW;IAC/C,yBAAyB;IACzB,8BAA8B;IAC9B,oDAAoD;IACpD,+BAA+B;IAC/B,sBAAsB;IACtB,aAAa;IACb,cAAc;CACjB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAEjB,MAAM,2BAA2B,GAAG,CAAC,UAAkB,EAAU,EAAE,CAC/D,CAAC,0BAA0B,EAAE,WAAW,UAAU,EAAE,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAEzE,MAAM,YAAY,GAAG,CAAC,KAAa,EAAW,EAAE;IAC5C,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAEhC,OAAO,KAAK,CAAC,MAAM,KAAK,iBAAiB,CAAC,MAAM;QAC5C,iBAAiB,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE,KAAK,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,SAAS,CAAC,CAAC,CAAC;AAC7F,CAAC,CAAC;AAEF,OAAO,EACH,eAAe,EACf,2BAA2B,EAC3B,gBAAgB,EAChB,uBAAuB,EACvB,YAAY,GACf,CAAC","sourcesContent":["const BUS_CONFIG_DOCTYPE =\n '<!DOCTYPE busconfig PUBLIC \"-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN\" ' +\n '\"https://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd\">';\n\nconst HEADLESS_RUNTIME_MARKER = \".gtkx-headless-runtime\";\n\nconst SWAY_CONFIG_LINES = [\n /^xwayland disable$/,\n /^default_border none$/,\n /^default_floating_border none$/,\n /^output HEADLESS-1 resolution [1-9]\\d*x[1-9]\\d*$/,\n /^output HEADLESS-1 bg #000000 solid_color$/,\n /^for_window \\[app_id=\"\\.\\*\"\\] floating enable, border none$/,\n /^for_window \\[title=\"\\.\\*\"\\] floating enable, border none$/,\n /^$/,\n];\n\nconst createSwayConfig = (width: string, height: string): string =>\n [\n \"xwayland disable\",\n \"default_border none\",\n \"default_floating_border none\",\n `output HEADLESS-1 resolution ${width}x${height}`,\n \"output HEADLESS-1 bg #000000 solid_color\",\n 'for_window [app_id=\".*\"] floating enable, border none',\n 'for_window [title=\".*\"] floating enable, border none',\n \"\",\n ].join(\"\\n\");\n\nconst createBusConfig = (busSocketPath: string): string =>\n [\n BUS_CONFIG_DOCTYPE,\n \"<busconfig>\",\n \" <type>session</type>\",\n ` <listen>unix:path=${busSocketPath}</listen>`,\n \" <auth>EXTERNAL</auth>\",\n ' <policy context=\"default\">',\n ' <allow send_destination=\"*\" eavesdrop=\"true\"/>',\n ' <allow eavesdrop=\"true\"/>',\n ' <allow own=\"*\"/>',\n \" </policy>\",\n \"</busconfig>\",\n ].join(\"\\n\");\n\nconst createHeadlessRuntimeMarker = (runtimeDir: string): string =>\n [\"gtkx-headless-runtime-v1\", `runtime=${runtimeDir}`, \"\"].join(\"\\n\");\n\nconst isSwayConfig = (value: string): boolean => {\n const lines = value.split(\"\\n\");\n\n return lines.length === SWAY_CONFIG_LINES.length &&\n SWAY_CONFIG_LINES.every((pattern, index) => pattern.test(lines[index] ?? \"invalid\"));\n};\n\nexport {\n createBusConfig,\n createHeadlessRuntimeMarker,\n createSwayConfig,\n HEADLESS_RUNTIME_MARKER,\n isSwayConfig,\n};\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"headless-display.d.ts","sourceRoot":"","sources":["../src/headless-display.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"headless-display.d.ts","sourceRoot":"","sources":["../src/headless-display.ts"],"names":[],"mappings":"AAeA;;GAEG;AACH,KAAK,YAAY,GAAG,MAAM,GAAG,QAAQ,CAAC;AAEtC;;GAEG;AACH,KAAK,eAAe,GAAG;IACnB,4EAA4E;IAC5E,IAAI,EAAE,MAAM,CAAC;IACb,4DAA4D;IAC5D,UAAU,EAAE,YAAY,CAAC;CAC5B,CAAC;AAoGF,QAAA,MAAM,mBAAmB;;;;;;;;;;;CAWxB,CAAC;AAEF,QAAA,MAAM,sBAAsB,GAAI,UAAU,OAAO,CAAC,eAAe,CAAC,KAAG,eAWpE,CAAC;AAqCF,QAAA,MAAM,mBAAmB,GAAI,QAAQ,eAAe,KAAG,OAAO,CAAC,eAAe,CAe7E,CAAC;AA4PF,QAAA,MAAM,oBAAoB,GAAU,SAAS,eAAe,KAAG,OAAO,CAAC,MAAM,IAAI,CAqDhF,CAAC;AAEF,OAAO,EACH,mBAAmB,EACnB,sBAAsB,EACtB,mBAAmB,EACnB,oBAAoB,EACpB,KAAK,YAAY,EACjB,KAAK,eAAe,GACvB,CAAC"}
|
package/dist/headless-display.js
CHANGED
|
@@ -4,13 +4,12 @@ import { chmodSync, closeSync, existsSync, mkdtempSync, openSync, rmSync, writeF
|
|
|
4
4
|
import { Socket } from "node:net";
|
|
5
5
|
import { tmpdir } from "node:os";
|
|
6
6
|
import { join } from "node:path";
|
|
7
|
+
import { createBusConfig, createHeadlessRuntimeMarker, createSwayConfig, HEADLESS_RUNTIME_MARKER, } from "./headless-config.js";
|
|
7
8
|
import { startNotificationService } from "./notification-service.js";
|
|
8
9
|
import { startVirtualSeat } from "./virtual-seat.js";
|
|
9
10
|
const DEFAULT_HEADLESS_SIZE = "1024x768";
|
|
10
11
|
const DEFAULT_HEADLESS_COMPOSITOR = "sway";
|
|
11
12
|
const HEADLESS_SIZE_PATTERN = /^[1-9]\d*x[1-9]\d*$/;
|
|
12
|
-
const BUS_CONFIG_DOCTYPE = '<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN" ' +
|
|
13
|
-
'"https://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">';
|
|
14
13
|
const hasWestonFakeSeat = createWestonFakeSeatProbe();
|
|
15
14
|
const compositorRegistry = {
|
|
16
15
|
sway: {
|
|
@@ -25,16 +24,7 @@ const compositorRegistry = {
|
|
|
25
24
|
},
|
|
26
25
|
start: (runtimeDir, width, height) => {
|
|
27
26
|
const configPath = join(runtimeDir, "sway.conf");
|
|
28
|
-
writeFileSync(configPath,
|
|
29
|
-
"xwayland disable",
|
|
30
|
-
"default_border none",
|
|
31
|
-
"default_floating_border none",
|
|
32
|
-
`output HEADLESS-1 resolution ${width}x${height}`,
|
|
33
|
-
"output HEADLESS-1 bg #000000 solid_color",
|
|
34
|
-
'for_window [app_id=".*"] floating enable, border none',
|
|
35
|
-
'for_window [title=".*"] floating enable, border none',
|
|
36
|
-
"",
|
|
37
|
-
].join("\n"));
|
|
27
|
+
writeFileSync(configPath, createSwayConfig(width, height));
|
|
38
28
|
return spawnWithParentDeathSupervisor("sway", ["-c", configPath], {
|
|
39
29
|
stdio: ["ignore", "ignore", "pipe"],
|
|
40
30
|
cleanupDirectory: runtimeDir,
|
|
@@ -131,19 +121,7 @@ const startCompositor = (runtimeDir, options, env) => {
|
|
|
131
121
|
const noVirtualSeat = () => undefined;
|
|
132
122
|
const attachVirtualSeat = (compositor, socketPath) => compositor.requiresVirtualSeat ? startVirtualSeat(socketPath) : Promise.resolve(noVirtualSeat);
|
|
133
123
|
const writeBusConfig = (busConfigPath, busSocketPath) => {
|
|
134
|
-
writeFileSync(busConfigPath,
|
|
135
|
-
BUS_CONFIG_DOCTYPE,
|
|
136
|
-
"<busconfig>",
|
|
137
|
-
" <type>session</type>",
|
|
138
|
-
` <listen>unix:path=${busSocketPath}</listen>`,
|
|
139
|
-
" <auth>EXTERNAL</auth>",
|
|
140
|
-
' <policy context="default">',
|
|
141
|
-
' <allow send_destination="*" eavesdrop="true"/>',
|
|
142
|
-
' <allow eavesdrop="true"/>',
|
|
143
|
-
' <allow own="*"/>',
|
|
144
|
-
" </policy>",
|
|
145
|
-
"</busconfig>",
|
|
146
|
-
].join("\n"));
|
|
124
|
+
writeFileSync(busConfigPath, createBusConfig(busSocketPath));
|
|
147
125
|
};
|
|
148
126
|
const exitedMessage = (label, path, code, signal) => `${label} exited (code ${String(code)}, signal ${signal ?? "null"}) before ${path} appeared`;
|
|
149
127
|
const monitorChild = (child, label, path) => {
|
|
@@ -246,7 +224,7 @@ const captureCompositorStderr = (child, logPath) => {
|
|
|
246
224
|
const captured = [];
|
|
247
225
|
const stderr = child.stderr;
|
|
248
226
|
if (stderr === null) {
|
|
249
|
-
return { chunks: captured, stop: noVirtualSeat };
|
|
227
|
+
return { chunks: captured, logPath, stop: noVirtualSeat };
|
|
250
228
|
}
|
|
251
229
|
stderr.setEncoding("utf8");
|
|
252
230
|
const descriptor = openSync(logPath, "a");
|
|
@@ -263,6 +241,7 @@ const captureCompositorStderr = (child, logPath) => {
|
|
|
263
241
|
stderr.on("data", onData);
|
|
264
242
|
return {
|
|
265
243
|
chunks: captured,
|
|
244
|
+
logPath,
|
|
266
245
|
stop: () => {
|
|
267
246
|
stderr.removeListener("data", onData);
|
|
268
247
|
if (isOpen) {
|
|
@@ -272,11 +251,12 @@ const captureCompositorStderr = (child, logPath) => {
|
|
|
272
251
|
},
|
|
273
252
|
};
|
|
274
253
|
};
|
|
275
|
-
const compositorExitMessage = (code, signal,
|
|
276
|
-
|
|
277
|
-
|
|
254
|
+
const compositorExitMessage = (code, signal, captured) => `[gtkx] the headless compositor exited (code ${String(code)}, signal ${signal ?? "null"}); ` +
|
|
255
|
+
"every Wayland client in this worker has been severed. " +
|
|
256
|
+
`Its stderr log is kept at ${captured.logPath} until this worker exits.\n${captured.chunks.join("")}`;
|
|
257
|
+
const watchCompositorExit = (child, captured) => {
|
|
278
258
|
const report = (code, signal) => {
|
|
279
|
-
process.stderr.write(compositorExitMessage(code, signal,
|
|
259
|
+
process.stderr.write(compositorExitMessage(code, signal, captured));
|
|
280
260
|
};
|
|
281
261
|
child.on("exit", report);
|
|
282
262
|
return () => child.removeListener("exit", report);
|
|
@@ -300,10 +280,23 @@ const makeTeardown = (stops) => {
|
|
|
300
280
|
runCleanups(stops);
|
|
301
281
|
};
|
|
302
282
|
};
|
|
283
|
+
const createHeadlessRuntimeDirectory = () => {
|
|
284
|
+
const runtimeDir = mkdtempSync(join(tmpdir(), "gtkx-xdg-"));
|
|
285
|
+
try {
|
|
286
|
+
chmodSync(runtimeDir, 0o700);
|
|
287
|
+
const markerPath = join(runtimeDir, HEADLESS_RUNTIME_MARKER);
|
|
288
|
+
writeFileSync(markerPath, createHeadlessRuntimeMarker(runtimeDir), { flag: "wx", mode: 0o600 });
|
|
289
|
+
chmodSync(markerPath, 0o600);
|
|
290
|
+
return runtimeDir;
|
|
291
|
+
}
|
|
292
|
+
catch (error) {
|
|
293
|
+
rmSync(runtimeDir, { recursive: true, force: true });
|
|
294
|
+
throw error;
|
|
295
|
+
}
|
|
296
|
+
};
|
|
303
297
|
const startHeadlessDisplay = async (options) => {
|
|
304
298
|
const env = {};
|
|
305
|
-
const runtimeDir =
|
|
306
|
-
chmodSync(runtimeDir, 0o700);
|
|
299
|
+
const runtimeDir = createHeadlessRuntimeDirectory();
|
|
307
300
|
const spawned = [];
|
|
308
301
|
const removeRuntime = () => {
|
|
309
302
|
restoreEnv(env);
|
|
@@ -333,7 +326,7 @@ const startHeadlessDisplay = async (options) => {
|
|
|
333
326
|
const stopVirtualSeat = await attachVirtualSeat(compositor, compositorSocketPath);
|
|
334
327
|
const stopNotifications = await startNotificationService(`unix:path=${busSocketPath}`);
|
|
335
328
|
const capturedStderr = captureCompositorStderr(compositor.child, join(runtimeDir, "compositor.stderr.log"));
|
|
336
|
-
const stopExitWatch = watchCompositorExit(compositor.child, capturedStderr
|
|
329
|
+
const stopExitWatch = watchCompositorExit(compositor.child, capturedStderr);
|
|
337
330
|
return makeTeardown([
|
|
338
331
|
stopExitWatch,
|
|
339
332
|
capturedStderr.stop,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"headless-display.js","sourceRoot":"","sources":["../src/headless-display.ts"],"names":[],"mappings":"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","sourcesContent":["import { resolveExecutable, spawnWithParentDeathSignal, spawnWithParentDeathSupervisor } from \"@gtkx/utils\";\nimport { type ChildProcess, spawnSync } from \"node:child_process\";\nimport { chmodSync, closeSync, existsSync, mkdtempSync, openSync, rmSync, writeFileSync, writeSync } from \"node:fs\";\nimport { Socket } from \"node:net\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { startNotificationService } from \"./notification-service.ts\";\nimport { startVirtualSeat } from \"./virtual-seat.ts\";\n\n/**\n * Wayland compositors that can back a headless display.\n */\ntype CompositorId = \"sway\" | \"weston\";\n\n/**\n * Settings for the per-worker headless Wayland display.\n */\ntype HeadlessOptions = {\n /** Output resolution as a \"WIDTHxHEIGHT\" string, for example \"1024x768\". */\n size: string;\n /** Wayland compositor used to back the headless display. */\n compositor: CompositorId;\n};\n\ntype EnvSnapshot = Record<string, string | undefined>;\n\ntype CompositorDescriptor = {\n socket: string;\n env: Record<string, string>;\n requiresVirtualSeat: boolean;\n start: (runtimeDir: string, width: string, height: string) => ChildProcess;\n};\n\ntype SpawnedCompositor = {\n child: ChildProcess;\n socket: string;\n requiresVirtualSeat: boolean;\n};\n\ntype ChildMonitor = {\n label: string;\n path: string;\n read: () => string;\n failure: () => string | undefined;\n subscribe: (notify: (failure: string) => void) => () => void;\n stop: () => void;\n};\n\ntype WaitForSocketOptions = {\n monitor: ChildMonitor;\n guards?: ChildMonitor[];\n timeout?: number;\n};\n\ntype DisplaySockets = {\n compositorMonitor: ChildMonitor;\n busMonitor: ChildMonitor;\n};\n\ntype CapturedStderr = {\n chunks: string[];\n stop: () => void;\n};\n\ntype SocketWatch = {\n options: WaitForSocketOptions;\n resolve: () => void;\n reject: (error: Error) => void;\n};\n\nconst DEFAULT_HEADLESS_SIZE = \"1024x768\";\nconst DEFAULT_HEADLESS_COMPOSITOR: CompositorId = \"sway\";\nconst HEADLESS_SIZE_PATTERN = /^[1-9]\\d*x[1-9]\\d*$/;\n\nconst BUS_CONFIG_DOCTYPE =\n '<!DOCTYPE busconfig PUBLIC \"-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN\" ' +\n '\"https://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd\">';\n\nconst hasWestonFakeSeat = createWestonFakeSeatProbe();\n\nconst compositorRegistry: Record<CompositorId, CompositorDescriptor> = {\n sway: {\n socket: \"wayland-1\",\n requiresVirtualSeat: true,\n env: {\n WLR_BACKENDS: \"headless\",\n WLR_RENDERER: \"pixman\",\n WLR_RENDERER_ALLOW_SOFTWARE: \"1\",\n WLR_LIBINPUT_NO_DEVICES: \"1\",\n WLR_HEADLESS_OUTPUTS: \"1\",\n },\n start: (runtimeDir, width, height) => {\n const configPath = join(runtimeDir, \"sway.conf\");\n\n writeFileSync(\n configPath,\n [\n \"xwayland disable\",\n \"default_border none\",\n \"default_floating_border none\",\n `output HEADLESS-1 resolution ${width}x${height}`,\n \"output HEADLESS-1 bg #000000 solid_color\",\n 'for_window [app_id=\".*\"] floating enable, border none',\n 'for_window [title=\".*\"] floating enable, border none',\n \"\",\n ].join(\"\\n\"),\n );\n\n return spawnWithParentDeathSupervisor(\"sway\", [\"-c\", configPath], {\n stdio: [\"ignore\", \"ignore\", \"pipe\"],\n cleanupDirectory: runtimeDir,\n });\n },\n },\n weston: {\n socket: \"wayland-0\",\n requiresVirtualSeat: false,\n env: {},\n start: (_runtimeDir, width, height) =>\n spawnWithParentDeathSignal(\n \"weston\",\n [\n \"--backend=headless\",\n \"--renderer=pixman\",\n ...(hasWestonFakeSeat() ? [\"--fake-seat\"] : []),\n `--width=${width}`,\n `--height=${height}`,\n \"--socket=wayland-0\",\n ],\n { stdio: [\"ignore\", \"ignore\", \"pipe\"], cleanupDirectories: [_runtimeDir] },\n ),\n },\n};\n\nconst STATIC_HEADLESS_ENV = {\n GDK_BACKEND: \"wayland\",\n GDK_DISABLE: \"vulkan\",\n GDK_DEBUG: \"no-vsync\",\n GSK_RENDERER: \"cairo\",\n GTK_A11Y: \"test\",\n LIBGL_ALWAYS_SOFTWARE: \"1\",\n GST_GL_WINDOW: \"none\",\n GSETTINGS_BACKEND: \"memory\",\n ALSOFT_DRIVERS: \"null\",\n ALSOFT_LOGLEVEL: \"0\",\n};\n\nconst resolveHeadlessOptions = (provided: Partial<HeadlessOptions>): HeadlessOptions => {\n const size = provided.size ?? DEFAULT_HEADLESS_SIZE;\n\n if (!HEADLESS_SIZE_PATTERN.test(size)) {\n throw new Error(`Invalid headless display size: ${size}`);\n }\n\n return {\n size,\n compositor: provided.compositor ?? DEFAULT_HEADLESS_COMPOSITOR,\n };\n};\n\nconst applyEnv = (snapshot: EnvSnapshot, values: Record<string, string>): void => {\n for (const [name, value] of Object.entries(values)) {\n if (!Object.hasOwn(snapshot, name)) {\n snapshot[name] = process.env[name];\n }\n\n process.env[name] = value;\n }\n};\n\nconst restoreEnv = (snapshot: EnvSnapshot): void => {\n for (const [name, previous] of Object.entries(snapshot)) {\n if (previous === undefined) {\n Reflect.deleteProperty(process.env, name);\n } else {\n process.env[name] = previous;\n }\n }\n};\n\nfunction createWestonFakeSeatProbe(): () => boolean {\n let isSupported: boolean | undefined;\n\n return () => {\n if (isSupported === undefined) {\n const help = spawnSync(resolveExecutable(\"weston\"), [\"--help\"], { encoding: \"utf8\" });\n isSupported = `${help.stdout}${help.stderr}`.includes(\"--fake-seat\");\n }\n\n return isSupported;\n };\n}\n\nconst isCompositorId = (value: string): value is CompositorId => Object.hasOwn(compositorRegistry, value);\n\nconst readHeadlessOptions = (params: URLSearchParams): Partial<HeadlessOptions> => {\n const options: Partial<HeadlessOptions> = {};\n const size = params.get(\"size\");\n\n if (size !== null) {\n options.size = size;\n }\n\n const compositor = params.get(\"compositor\");\n\n if (compositor !== null && isCompositorId(compositor)) {\n options.compositor = compositor;\n }\n\n return options;\n};\n\nconst startCompositor = (runtimeDir: string, options: HeadlessOptions, env: EnvSnapshot): SpawnedCompositor => {\n const descriptor = compositorRegistry[options.compositor];\n const [width = \"\", height = \"\"] = options.size.split(\"x\", 2);\n applyEnv(env, descriptor.env);\n\n return {\n child: descriptor.start(runtimeDir, width, height),\n socket: descriptor.socket,\n requiresVirtualSeat: descriptor.requiresVirtualSeat,\n };\n};\n\nconst noVirtualSeat = (): void => undefined;\n\nconst attachVirtualSeat = (compositor: SpawnedCompositor, socketPath: string): Promise<() => void> =>\n compositor.requiresVirtualSeat ? startVirtualSeat(socketPath) : Promise.resolve(noVirtualSeat);\n\nconst writeBusConfig = (busConfigPath: string, busSocketPath: string): void => {\n writeFileSync(\n busConfigPath,\n [\n BUS_CONFIG_DOCTYPE,\n \"<busconfig>\",\n \" <type>session</type>\",\n ` <listen>unix:path=${busSocketPath}</listen>`,\n \" <auth>EXTERNAL</auth>\",\n ' <policy context=\"default\">',\n ' <allow send_destination=\"*\" eavesdrop=\"true\"/>',\n ' <allow eavesdrop=\"true\"/>',\n ' <allow own=\"*\"/>',\n \" </policy>\",\n \"</busconfig>\",\n ].join(\"\\n\"),\n );\n};\n\nconst exitedMessage = (label: string, path: string, code: number | null, signal: NodeJS.Signals | null): string =>\n `${label} exited (code ${String(code)}, signal ${signal ?? \"null\"}) before ${path} appeared`;\n\nconst monitorChild = (child: ChildProcess, label: string, path: string): ChildMonitor => {\n const stderr = child.stderr;\n const subscribers: Set<(failure: string) => void> = new Set();\n let log = \"\";\n let failure: string | undefined;\n\n const onData = (chunk: string): void => {\n log += chunk;\n };\n\n stderr?.setEncoding(\"utf8\");\n stderr?.on(\"data\", onData);\n\n const record = (cause: string): void => {\n failure ??= cause;\n\n for (const notify of subscribers) {\n notify(cause);\n }\n };\n\n child.on(\"close\", (code: number | null, signal: NodeJS.Signals | null) => {\n record(`${exitedMessage(label, path, code, signal)}\\n${log}`);\n });\n\n child.on(\"error\", (cause: Error) => {\n record(`${label} failed to spawn before ${path} appeared: ${cause.message}\\n${log}`);\n });\n\n return {\n label,\n path,\n read: () => log,\n failure: () => failure,\n subscribe: (notify) => {\n subscribers.add(notify);\n\n return (): void => {\n subscribers.delete(notify);\n };\n },\n stop: () => {\n stderr?.removeListener(\"data\", onData);\n stderr?.resume();\n\n if (stderr instanceof Socket) {\n stderr.unref();\n }\n },\n };\n};\n\nconst runCleanups = (cleanups: (() => void)[]): void => {\n for (const cleanup of cleanups) {\n cleanup();\n }\n\n cleanups.length = 0;\n};\n\nconst pollForPath = (path: string, onFound: () => void): NodeJS.Timeout =>\n setInterval(() => {\n if (existsSync(path)) {\n onFound();\n }\n }, 50);\n\nconst firstFailure = (monitors: ChildMonitor[]): string | undefined => {\n for (const monitor of monitors) {\n const failure = monitor.failure();\n\n if (failure !== undefined) {\n return failure;\n }\n }\n\n return undefined;\n};\n\nconst watchForSocket = ({ options, resolve, reject }: SocketWatch): void => {\n const { monitor, guards = [], timeout = 15_000 } = options;\n const watched = [monitor, ...guards];\n const cleanups: (() => void)[] = [monitor.stop];\n\n const stop = (): void => {\n runCleanups(cleanups);\n };\n\n const fail = (message: string): void => {\n stop();\n\n for (const guard of guards) {\n guard.stop();\n }\n\n reject(new Error(message));\n };\n\n const alreadyFailed = firstFailure(watched);\n\n if (alreadyFailed !== undefined) {\n fail(alreadyFailed);\n\n return;\n }\n\n const poll = pollForPath(monitor.path, () => {\n stop();\n resolve();\n });\n\n const timer = setTimeout(() => {\n fail(`${monitor.label} did not become available within ${String(timeout)}ms\\n${monitor.read()}`);\n }, timeout);\n\n cleanups.push(\n () => {\n clearInterval(poll);\n clearTimeout(timer);\n },\n ...watched.map((entry) => entry.subscribe(fail)),\n );\n};\n\nconst waitForSocket = (options: WaitForSocketOptions): Promise<void> =>\n new Promise((resolve, reject) => {\n watchForSocket({ options, resolve, reject });\n });\n\nconst captureCompositorStderr = (child: ChildProcess, logPath: string): CapturedStderr => {\n const captured: string[] = [];\n const stderr = child.stderr;\n\n if (stderr === null) {\n return { chunks: captured, stop: noVirtualSeat };\n }\n\n stderr.setEncoding(\"utf8\");\n const descriptor = openSync(logPath, \"a\");\n let isOpen = true;\n\n const onData = (chunk: string): void => {\n captured.push(chunk);\n\n try {\n writeSync(descriptor, chunk);\n } catch {\n return;\n }\n };\n\n stderr.on(\"data\", onData);\n\n return {\n chunks: captured,\n stop: () => {\n stderr.removeListener(\"data\", onData);\n\n if (isOpen) {\n isOpen = false;\n closeSync(descriptor);\n }\n },\n };\n};\n\nconst compositorExitMessage = (\n code: number | null,\n signal: NodeJS.Signals | null,\n capturedStderr: string[],\n): string =>\n `[gtkx] the headless compositor exited (code ${String(code)}, signal ${signal ?? \"null\"}); ` +\n `every Wayland client in this worker has been severed.\\n${capturedStderr.join(\"\")}`;\n\nconst watchCompositorExit = (child: ChildProcess, capturedStderr: string[]): (() => void) => {\n const report = (code: number | null, signal: NodeJS.Signals | null): void => {\n process.stderr.write(compositorExitMessage(code, signal, capturedStderr));\n };\n\n child.on(\"exit\", report);\n\n return () => child.removeListener(\"exit\", report);\n};\n\nconst waitForDisplaySockets = async ({ compositorMonitor, busMonitor }: DisplaySockets): Promise<void> => {\n await waitForSocket({ monitor: busMonitor, guards: [compositorMonitor] });\n await waitForSocket({ monitor: compositorMonitor });\n};\n\nconst killSpawned = (children: ChildProcess[]): void => {\n for (const child of children) {\n child.kill(\"SIGTERM\");\n }\n};\n\nconst makeTeardown = (stops: (() => void)[]): (() => void) => {\n let isTorndown = false;\n\n return (): void => {\n if (isTorndown) {\n return;\n }\n\n isTorndown = true;\n runCleanups(stops);\n };\n};\n\nconst startHeadlessDisplay = async (options: HeadlessOptions): Promise<() => void> => {\n const env: EnvSnapshot = {};\n const runtimeDir = mkdtempSync(join(tmpdir(), \"gtkx-xdg-\"));\n chmodSync(runtimeDir, 0o700);\n const spawned: ChildProcess[] = [];\n\n const removeRuntime = (): void => {\n restoreEnv(env);\n rmSync(runtimeDir, { recursive: true, force: true });\n };\n\n try {\n applyEnv(env, STATIC_HEADLESS_ENV);\n applyEnv(env, { XDG_RUNTIME_DIR: runtimeDir });\n const busConfigPath = join(runtimeDir, \"session.conf\");\n const busSocketPath = join(runtimeDir, \"bus\");\n writeBusConfig(busConfigPath, busSocketPath);\n\n const busChild = spawnWithParentDeathSignal(\"dbus-daemon\", [`--config-file=${busConfigPath}`], {\n stdio: [\"ignore\", \"ignore\", \"pipe\"],\n cleanupDirectories: [runtimeDir],\n });\n\n busChild.unref();\n spawned.push(busChild);\n const busMonitor = monitorChild(busChild, \"D-Bus session bus\", busSocketPath);\n applyEnv(env, { DBUS_SESSION_BUS_ADDRESS: `unix:path=${busSocketPath}` });\n const compositor = startCompositor(runtimeDir, options, env);\n compositor.child.unref();\n spawned.push(compositor.child);\n const compositorSocketPath = join(runtimeDir, compositor.socket);\n const compositorMonitor = monitorChild(compositor.child, \"Compositor\", compositorSocketPath);\n applyEnv(env, { WAYLAND_DISPLAY: compositor.socket });\n await waitForDisplaySockets({ compositorMonitor, busMonitor });\n const stopVirtualSeat = await attachVirtualSeat(compositor, compositorSocketPath);\n const stopNotifications = await startNotificationService(`unix:path=${busSocketPath}`);\n const capturedStderr = captureCompositorStderr(compositor.child, join(runtimeDir, \"compositor.stderr.log\"));\n const stopExitWatch = watchCompositorExit(compositor.child, capturedStderr.chunks);\n\n return makeTeardown([\n stopExitWatch,\n capturedStderr.stop,\n () => {\n killSpawned(spawned);\n },\n stopVirtualSeat,\n stopNotifications,\n removeRuntime,\n ]);\n } catch (error) {\n killSpawned(spawned);\n removeRuntime();\n throw error;\n }\n};\n\nexport {\n STATIC_HEADLESS_ENV,\n resolveHeadlessOptions,\n readHeadlessOptions,\n startHeadlessDisplay,\n type CompositorId,\n type HeadlessOptions,\n};\n"]}
|
|
1
|
+
{"version":3,"file":"headless-display.js","sourceRoot":"","sources":["../src/headless-display.ts"],"names":[],"mappings":"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","sourcesContent":["import { resolveExecutable, spawnWithParentDeathSignal, spawnWithParentDeathSupervisor } from \"@gtkx/utils\";\nimport { type ChildProcess, spawnSync } from \"node:child_process\";\nimport { chmodSync, closeSync, existsSync, mkdtempSync, openSync, rmSync, writeFileSync, writeSync } from \"node:fs\";\nimport { Socket } from \"node:net\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport {\n createBusConfig,\n createHeadlessRuntimeMarker,\n createSwayConfig,\n HEADLESS_RUNTIME_MARKER,\n} from \"./headless-config.ts\";\nimport { startNotificationService } from \"./notification-service.ts\";\nimport { startVirtualSeat } from \"./virtual-seat.ts\";\n\n/**\n * Wayland compositors that can back a headless display.\n */\ntype CompositorId = \"sway\" | \"weston\";\n\n/**\n * Settings for the per-worker headless Wayland display.\n */\ntype HeadlessOptions = {\n /** Output resolution as a \"WIDTHxHEIGHT\" string, for example \"1024x768\". */\n size: string;\n /** Wayland compositor used to back the headless display. */\n compositor: CompositorId;\n};\n\ntype EnvSnapshot = Record<string, string | undefined>;\n\ntype CompositorDescriptor = {\n socket: string;\n env: Record<string, string>;\n requiresVirtualSeat: boolean;\n start: (runtimeDir: string, width: string, height: string) => ChildProcess;\n};\n\ntype SpawnedCompositor = {\n child: ChildProcess;\n socket: string;\n requiresVirtualSeat: boolean;\n};\n\ntype ChildMonitor = {\n label: string;\n path: string;\n read: () => string;\n failure: () => string | undefined;\n subscribe: (notify: (failure: string) => void) => () => void;\n stop: () => void;\n};\n\ntype WaitForSocketOptions = {\n monitor: ChildMonitor;\n guards?: ChildMonitor[];\n timeout?: number;\n};\n\ntype DisplaySockets = {\n compositorMonitor: ChildMonitor;\n busMonitor: ChildMonitor;\n};\n\ntype CapturedStderr = {\n chunks: string[];\n logPath: string;\n stop: () => void;\n};\n\ntype SocketWatch = {\n options: WaitForSocketOptions;\n resolve: () => void;\n reject: (error: Error) => void;\n};\n\nconst DEFAULT_HEADLESS_SIZE = \"1024x768\";\nconst DEFAULT_HEADLESS_COMPOSITOR: CompositorId = \"sway\";\nconst HEADLESS_SIZE_PATTERN = /^[1-9]\\d*x[1-9]\\d*$/;\n\nconst hasWestonFakeSeat = createWestonFakeSeatProbe();\n\nconst compositorRegistry: Record<CompositorId, CompositorDescriptor> = {\n sway: {\n socket: \"wayland-1\",\n requiresVirtualSeat: true,\n env: {\n WLR_BACKENDS: \"headless\",\n WLR_RENDERER: \"pixman\",\n WLR_RENDERER_ALLOW_SOFTWARE: \"1\",\n WLR_LIBINPUT_NO_DEVICES: \"1\",\n WLR_HEADLESS_OUTPUTS: \"1\",\n },\n start: (runtimeDir, width, height) => {\n const configPath = join(runtimeDir, \"sway.conf\");\n\n writeFileSync(\n configPath,\n createSwayConfig(width, height),\n );\n\n return spawnWithParentDeathSupervisor(\"sway\", [\"-c\", configPath], {\n stdio: [\"ignore\", \"ignore\", \"pipe\"],\n cleanupDirectory: runtimeDir,\n });\n },\n },\n weston: {\n socket: \"wayland-0\",\n requiresVirtualSeat: false,\n env: {},\n start: (_runtimeDir, width, height) =>\n spawnWithParentDeathSignal(\n \"weston\",\n [\n \"--backend=headless\",\n \"--renderer=pixman\",\n ...(hasWestonFakeSeat() ? [\"--fake-seat\"] : []),\n `--width=${width}`,\n `--height=${height}`,\n \"--socket=wayland-0\",\n ],\n { stdio: [\"ignore\", \"ignore\", \"pipe\"], cleanupDirectories: [_runtimeDir] },\n ),\n },\n};\n\nconst STATIC_HEADLESS_ENV = {\n GDK_BACKEND: \"wayland\",\n GDK_DISABLE: \"vulkan\",\n GDK_DEBUG: \"no-vsync\",\n GSK_RENDERER: \"cairo\",\n GTK_A11Y: \"test\",\n LIBGL_ALWAYS_SOFTWARE: \"1\",\n GST_GL_WINDOW: \"none\",\n GSETTINGS_BACKEND: \"memory\",\n ALSOFT_DRIVERS: \"null\",\n ALSOFT_LOGLEVEL: \"0\",\n};\n\nconst resolveHeadlessOptions = (provided: Partial<HeadlessOptions>): HeadlessOptions => {\n const size = provided.size ?? DEFAULT_HEADLESS_SIZE;\n\n if (!HEADLESS_SIZE_PATTERN.test(size)) {\n throw new Error(`Invalid headless display size: ${size}`);\n }\n\n return {\n size,\n compositor: provided.compositor ?? DEFAULT_HEADLESS_COMPOSITOR,\n };\n};\n\nconst applyEnv = (snapshot: EnvSnapshot, values: Record<string, string>): void => {\n for (const [name, value] of Object.entries(values)) {\n if (!Object.hasOwn(snapshot, name)) {\n snapshot[name] = process.env[name];\n }\n\n process.env[name] = value;\n }\n};\n\nconst restoreEnv = (snapshot: EnvSnapshot): void => {\n for (const [name, previous] of Object.entries(snapshot)) {\n if (previous === undefined) {\n Reflect.deleteProperty(process.env, name);\n } else {\n process.env[name] = previous;\n }\n }\n};\n\nfunction createWestonFakeSeatProbe(): () => boolean {\n let isSupported: boolean | undefined;\n\n return () => {\n if (isSupported === undefined) {\n const help = spawnSync(resolveExecutable(\"weston\"), [\"--help\"], { encoding: \"utf8\" });\n isSupported = `${help.stdout}${help.stderr}`.includes(\"--fake-seat\");\n }\n\n return isSupported;\n };\n}\n\nconst isCompositorId = (value: string): value is CompositorId => Object.hasOwn(compositorRegistry, value);\n\nconst readHeadlessOptions = (params: URLSearchParams): Partial<HeadlessOptions> => {\n const options: Partial<HeadlessOptions> = {};\n const size = params.get(\"size\");\n\n if (size !== null) {\n options.size = size;\n }\n\n const compositor = params.get(\"compositor\");\n\n if (compositor !== null && isCompositorId(compositor)) {\n options.compositor = compositor;\n }\n\n return options;\n};\n\nconst startCompositor = (runtimeDir: string, options: HeadlessOptions, env: EnvSnapshot): SpawnedCompositor => {\n const descriptor = compositorRegistry[options.compositor];\n const [width = \"\", height = \"\"] = options.size.split(\"x\", 2);\n applyEnv(env, descriptor.env);\n\n return {\n child: descriptor.start(runtimeDir, width, height),\n socket: descriptor.socket,\n requiresVirtualSeat: descriptor.requiresVirtualSeat,\n };\n};\n\nconst noVirtualSeat = (): void => undefined;\n\nconst attachVirtualSeat = (compositor: SpawnedCompositor, socketPath: string): Promise<() => void> =>\n compositor.requiresVirtualSeat ? startVirtualSeat(socketPath) : Promise.resolve(noVirtualSeat);\n\nconst writeBusConfig = (busConfigPath: string, busSocketPath: string): void => {\n writeFileSync(busConfigPath, createBusConfig(busSocketPath));\n};\n\nconst exitedMessage = (label: string, path: string, code: number | null, signal: NodeJS.Signals | null): string =>\n `${label} exited (code ${String(code)}, signal ${signal ?? \"null\"}) before ${path} appeared`;\n\nconst monitorChild = (child: ChildProcess, label: string, path: string): ChildMonitor => {\n const stderr = child.stderr;\n const subscribers: Set<(failure: string) => void> = new Set();\n let log = \"\";\n let failure: string | undefined;\n\n const onData = (chunk: string): void => {\n log += chunk;\n };\n\n stderr?.setEncoding(\"utf8\");\n stderr?.on(\"data\", onData);\n\n const record = (cause: string): void => {\n failure ??= cause;\n\n for (const notify of subscribers) {\n notify(cause);\n }\n };\n\n child.on(\"close\", (code: number | null, signal: NodeJS.Signals | null) => {\n record(`${exitedMessage(label, path, code, signal)}\\n${log}`);\n });\n\n child.on(\"error\", (cause: Error) => {\n record(`${label} failed to spawn before ${path} appeared: ${cause.message}\\n${log}`);\n });\n\n return {\n label,\n path,\n read: () => log,\n failure: () => failure,\n subscribe: (notify) => {\n subscribers.add(notify);\n\n return (): void => {\n subscribers.delete(notify);\n };\n },\n stop: () => {\n stderr?.removeListener(\"data\", onData);\n stderr?.resume();\n\n if (stderr instanceof Socket) {\n stderr.unref();\n }\n },\n };\n};\n\nconst runCleanups = (cleanups: (() => void)[]): void => {\n for (const cleanup of cleanups) {\n cleanup();\n }\n\n cleanups.length = 0;\n};\n\nconst pollForPath = (path: string, onFound: () => void): NodeJS.Timeout =>\n setInterval(() => {\n if (existsSync(path)) {\n onFound();\n }\n }, 50);\n\nconst firstFailure = (monitors: ChildMonitor[]): string | undefined => {\n for (const monitor of monitors) {\n const failure = monitor.failure();\n\n if (failure !== undefined) {\n return failure;\n }\n }\n\n return undefined;\n};\n\nconst watchForSocket = ({ options, resolve, reject }: SocketWatch): void => {\n const { monitor, guards = [], timeout = 15_000 } = options;\n const watched = [monitor, ...guards];\n const cleanups: (() => void)[] = [monitor.stop];\n\n const stop = (): void => {\n runCleanups(cleanups);\n };\n\n const fail = (message: string): void => {\n stop();\n\n for (const guard of guards) {\n guard.stop();\n }\n\n reject(new Error(message));\n };\n\n const alreadyFailed = firstFailure(watched);\n\n if (alreadyFailed !== undefined) {\n fail(alreadyFailed);\n\n return;\n }\n\n const poll = pollForPath(monitor.path, () => {\n stop();\n resolve();\n });\n\n const timer = setTimeout(() => {\n fail(`${monitor.label} did not become available within ${String(timeout)}ms\\n${monitor.read()}`);\n }, timeout);\n\n cleanups.push(\n () => {\n clearInterval(poll);\n clearTimeout(timer);\n },\n ...watched.map((entry) => entry.subscribe(fail)),\n );\n};\n\nconst waitForSocket = (options: WaitForSocketOptions): Promise<void> =>\n new Promise((resolve, reject) => {\n watchForSocket({ options, resolve, reject });\n });\n\nconst captureCompositorStderr = (child: ChildProcess, logPath: string): CapturedStderr => {\n const captured: string[] = [];\n const stderr = child.stderr;\n\n if (stderr === null) {\n return { chunks: captured, logPath, stop: noVirtualSeat };\n }\n\n stderr.setEncoding(\"utf8\");\n const descriptor = openSync(logPath, \"a\");\n let isOpen = true;\n\n const onData = (chunk: string): void => {\n captured.push(chunk);\n\n try {\n writeSync(descriptor, chunk);\n } catch {\n return;\n }\n };\n\n stderr.on(\"data\", onData);\n\n return {\n chunks: captured,\n logPath,\n stop: () => {\n stderr.removeListener(\"data\", onData);\n\n if (isOpen) {\n isOpen = false;\n closeSync(descriptor);\n }\n },\n };\n};\n\nconst compositorExitMessage = (\n code: number | null,\n signal: NodeJS.Signals | null,\n captured: CapturedStderr,\n): string =>\n `[gtkx] the headless compositor exited (code ${String(code)}, signal ${signal ?? \"null\"}); ` +\n \"every Wayland client in this worker has been severed. \" +\n `Its stderr log is kept at ${captured.logPath} until this worker exits.\\n${captured.chunks.join(\"\")}`;\n\nconst watchCompositorExit = (child: ChildProcess, captured: CapturedStderr): (() => void) => {\n const report = (code: number | null, signal: NodeJS.Signals | null): void => {\n process.stderr.write(compositorExitMessage(code, signal, captured));\n };\n\n child.on(\"exit\", report);\n\n return () => child.removeListener(\"exit\", report);\n};\n\nconst waitForDisplaySockets = async ({ compositorMonitor, busMonitor }: DisplaySockets): Promise<void> => {\n await waitForSocket({ monitor: busMonitor, guards: [compositorMonitor] });\n await waitForSocket({ monitor: compositorMonitor });\n};\n\nconst killSpawned = (children: ChildProcess[]): void => {\n for (const child of children) {\n child.kill(\"SIGTERM\");\n }\n};\n\nconst makeTeardown = (stops: (() => void)[]): (() => void) => {\n let isTorndown = false;\n\n return (): void => {\n if (isTorndown) {\n return;\n }\n\n isTorndown = true;\n runCleanups(stops);\n };\n};\n\nconst createHeadlessRuntimeDirectory = (): string => {\n const runtimeDir = mkdtempSync(join(tmpdir(), \"gtkx-xdg-\"));\n\n try {\n chmodSync(runtimeDir, 0o700);\n const markerPath = join(runtimeDir, HEADLESS_RUNTIME_MARKER);\n writeFileSync(markerPath, createHeadlessRuntimeMarker(runtimeDir), { flag: \"wx\", mode: 0o600 });\n chmodSync(markerPath, 0o600);\n\n return runtimeDir;\n } catch (error) {\n rmSync(runtimeDir, { recursive: true, force: true });\n throw error;\n }\n};\n\nconst startHeadlessDisplay = async (options: HeadlessOptions): Promise<() => void> => {\n const env: EnvSnapshot = {};\n const runtimeDir = createHeadlessRuntimeDirectory();\n const spawned: ChildProcess[] = [];\n\n const removeRuntime = (): void => {\n restoreEnv(env);\n rmSync(runtimeDir, { recursive: true, force: true });\n };\n\n try {\n applyEnv(env, STATIC_HEADLESS_ENV);\n applyEnv(env, { XDG_RUNTIME_DIR: runtimeDir });\n const busConfigPath = join(runtimeDir, \"session.conf\");\n const busSocketPath = join(runtimeDir, \"bus\");\n writeBusConfig(busConfigPath, busSocketPath);\n\n const busChild = spawnWithParentDeathSignal(\"dbus-daemon\", [`--config-file=${busConfigPath}`], {\n stdio: [\"ignore\", \"ignore\", \"pipe\"],\n cleanupDirectories: [runtimeDir],\n });\n\n busChild.unref();\n spawned.push(busChild);\n const busMonitor = monitorChild(busChild, \"D-Bus session bus\", busSocketPath);\n applyEnv(env, { DBUS_SESSION_BUS_ADDRESS: `unix:path=${busSocketPath}` });\n const compositor = startCompositor(runtimeDir, options, env);\n compositor.child.unref();\n spawned.push(compositor.child);\n const compositorSocketPath = join(runtimeDir, compositor.socket);\n const compositorMonitor = monitorChild(compositor.child, \"Compositor\", compositorSocketPath);\n applyEnv(env, { WAYLAND_DISPLAY: compositor.socket });\n await waitForDisplaySockets({ compositorMonitor, busMonitor });\n const stopVirtualSeat = await attachVirtualSeat(compositor, compositorSocketPath);\n const stopNotifications = await startNotificationService(`unix:path=${busSocketPath}`);\n const capturedStderr = captureCompositorStderr(compositor.child, join(runtimeDir, \"compositor.stderr.log\"));\n const stopExitWatch = watchCompositorExit(compositor.child, capturedStderr);\n\n return makeTeardown([\n stopExitWatch,\n capturedStderr.stop,\n () => {\n killSpawned(spawned);\n },\n stopVirtualSeat,\n stopNotifications,\n removeRuntime,\n ]);\n } catch (error) {\n killSpawned(spawned);\n removeRuntime();\n throw error;\n }\n};\n\nexport {\n STATIC_HEADLESS_ENV,\n resolveHeadlessOptions,\n readHeadlessOptions,\n startHeadlessDisplay,\n type CompositorId,\n type HeadlessOptions,\n};\n"]}
|
package/dist/headless.d.ts
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
1
|
export { readHeadlessOptions, resolveHeadlessOptions, startHeadlessDisplay, STATIC_HEADLESS_ENV, type CompositorId, type HeadlessOptions, } from "./headless-display.js";
|
|
2
|
+
export { findStaleHeadlessDisplays, reapStaleHeadlessDisplays, reapStaleHeadlessDisplaysAtStartup, type StaleHeadlessDisplay, } from "./reap-headless-displays.js";
|
|
2
3
|
//# sourceMappingURL=headless.d.ts.map
|
package/dist/headless.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"headless.d.ts","sourceRoot":"","sources":["../src/headless.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,mBAAmB,EACnB,sBAAsB,EACtB,oBAAoB,EACpB,mBAAmB,EACnB,KAAK,YAAY,EACjB,KAAK,eAAe,GACvB,MAAM,uBAAuB,CAAC"}
|
|
1
|
+
{"version":3,"file":"headless.d.ts","sourceRoot":"","sources":["../src/headless.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,mBAAmB,EACnB,sBAAsB,EACtB,oBAAoB,EACpB,mBAAmB,EACnB,KAAK,YAAY,EACjB,KAAK,eAAe,GACvB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACH,yBAAyB,EACzB,yBAAyB,EACzB,kCAAkC,EAClC,KAAK,oBAAoB,GAC5B,MAAM,6BAA6B,CAAC"}
|
package/dist/headless.js
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
1
|
export { readHeadlessOptions, resolveHeadlessOptions, startHeadlessDisplay, STATIC_HEADLESS_ENV, } from "./headless-display.js";
|
|
2
|
+
export { findStaleHeadlessDisplays, reapStaleHeadlessDisplays, reapStaleHeadlessDisplaysAtStartup, } from "./reap-headless-displays.js";
|
|
2
3
|
//# sourceMappingURL=headless.js.map
|
package/dist/headless.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"headless.js","sourceRoot":"","sources":["../src/headless.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,mBAAmB,EACnB,sBAAsB,EACtB,oBAAoB,EACpB,mBAAmB,GAGtB,MAAM,uBAAuB,CAAC","sourcesContent":["export {\n readHeadlessOptions,\n resolveHeadlessOptions,\n startHeadlessDisplay,\n STATIC_HEADLESS_ENV,\n type CompositorId,\n type HeadlessOptions,\n} from \"./headless-display.js\";\n"]}
|
|
1
|
+
{"version":3,"file":"headless.js","sourceRoot":"","sources":["../src/headless.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,mBAAmB,EACnB,sBAAsB,EACtB,oBAAoB,EACpB,mBAAmB,GAGtB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACH,yBAAyB,EACzB,yBAAyB,EACzB,kCAAkC,GAErC,MAAM,6BAA6B,CAAC","sourcesContent":["export {\n readHeadlessOptions,\n resolveHeadlessOptions,\n startHeadlessDisplay,\n STATIC_HEADLESS_ENV,\n type CompositorId,\n type HeadlessOptions,\n} from \"./headless-display.js\";\nexport {\n findStaleHeadlessDisplays,\n reapStaleHeadlessDisplays,\n reapStaleHeadlessDisplaysAtStartup,\n type StaleHeadlessDisplay,\n} from \"./reap-headless-displays.js\";\n"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -10,6 +10,12 @@ type PluginOptions = Partial<HeadlessOptions> & Partial<Record<"configFile", str
|
|
|
10
10
|
* Wayland display. It configures the forks pool, injects the worker preload and
|
|
11
11
|
* setup files, and sets the environment needed for headless GTK4 rendering.
|
|
12
12
|
*
|
|
13
|
+
* Each worker's compositor, session bus, and private runtime directory are torn
|
|
14
|
+
* down when the worker exits, and a guard process tears them down as well when
|
|
15
|
+
* the worker or the Vitest process that launched it is killed with `SIGKILL`.
|
|
16
|
+
* Creating the plugin reaps stale `gtkx-xdg-*` runtime directories left behind
|
|
17
|
+
* by earlier runs, which `gtkx cleanup` also does on demand.
|
|
18
|
+
*
|
|
13
19
|
* @param options Headless display settings (size, compositor) forwarded to each worker.
|
|
14
20
|
* @returns A Vitest config plugin.
|
|
15
21
|
*/
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAM5C,OAAO,EAAE,KAAK,eAAe,EAAuB,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAM5C,OAAO,EAAE,KAAK,eAAe,EAAuB,MAAM,uBAAuB,CAAC;AAGlF;;;GAGG;AACH,KAAK,aAAa,GAAG,OAAO,CAAC,eAAe,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,YAAY,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;AAsBlG;;;;;;;;;;;;;GAaG;AACH,QAAA,MAAM,IAAI,GAAI,UAAS,aAAkB,KAAG,MA2B3C,CAAC;AAEF,eAAe,IAAI,CAAC;AACpB,OAAO,EAAE,KAAK,YAAY,EAAE,KAAK,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAChF,OAAO,EAAE,KAAK,aAAa,EAAE,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -4,6 +4,7 @@ import { existsSync } from "node:fs";
|
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { pathToFileURL } from "node:url";
|
|
6
6
|
import { STATIC_HEADLESS_ENV } from "./headless-display.js";
|
|
7
|
+
import { reapStaleHeadlessDisplaysAtStartup } from "./reap-headless-displays.js";
|
|
7
8
|
const GTKX_INLINE_DEPS = [/@gtkx\/(?!native)/, /[/\\]\.gtkx[/\\]/];
|
|
8
9
|
const DEFAULT_TIMEOUT = 30_000;
|
|
9
10
|
const workerPreloadUrl = () => {
|
|
@@ -23,11 +24,18 @@ const headlessPreloadSpecifier = (options) => {
|
|
|
23
24
|
* Wayland display. It configures the forks pool, injects the worker preload and
|
|
24
25
|
* setup files, and sets the environment needed for headless GTK4 rendering.
|
|
25
26
|
*
|
|
27
|
+
* Each worker's compositor, session bus, and private runtime directory are torn
|
|
28
|
+
* down when the worker exits, and a guard process tears them down as well when
|
|
29
|
+
* the worker or the Vitest process that launched it is killed with `SIGKILL`.
|
|
30
|
+
* Creating the plugin reaps stale `gtkx-xdg-*` runtime directories left behind
|
|
31
|
+
* by earlier runs, which `gtkx cleanup` also does on demand.
|
|
32
|
+
*
|
|
26
33
|
* @param options Headless display settings (size, compositor) forwarded to each worker.
|
|
27
34
|
* @returns A Vitest config plugin.
|
|
28
35
|
*/
|
|
29
36
|
const gtkx = (options = {}) => {
|
|
30
37
|
assertSupportedNodeVersion();
|
|
38
|
+
reapStaleHeadlessDisplaysAtStartup();
|
|
31
39
|
const { configFile, ...headlessOptions } = options;
|
|
32
40
|
const loadConfig = createConfigLoader({ configFile });
|
|
33
41
|
return createConfigPlugin({
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,0BAA0B,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AACvF,OAAO,kBAAkB,MAAM,0BAA0B,CAAC;AAC1D,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAwB,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,0BAA0B,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AACvF,OAAO,kBAAkB,MAAM,0BAA0B,CAAC;AAC1D,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAwB,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAClF,OAAO,EAAE,kCAAkC,EAAE,MAAM,6BAA6B,CAAC;AAQjF,MAAM,gBAAgB,GAAa,CAAC,mBAAmB,EAAE,kBAAkB,CAAC,CAAC;AAC7E,MAAM,eAAe,GAAG,MAAM,CAAC;AAE/B,MAAM,gBAAgB,GAAG,GAAQ,EAAE;IAC/B,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,mBAAmB,CAAC,CAAC;IAC/D,MAAM,IAAI,GAAG,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,mBAAmB,CAAC,CAAC;IAE1G,OAAO,aAAa,CAAC,IAAI,CAAC,CAAC;AAC/B,CAAC,CAAC;AAEF,MAAM,wBAAwB,GAAG,CAAC,OAAiC,EAAU,EAAE;IAC3E,MAAM,GAAG,GAAG,gBAAgB,EAAE,CAAC;IAE/B,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QACjD,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IACrC,CAAC;IAED,OAAO,GAAG,CAAC,IAAI,CAAC;AACpB,CAAC,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,MAAM,IAAI,GAAG,CAAC,UAAyB,EAAE,EAAU,EAAE;IACjD,0BAA0B,EAAE,CAAC;IAC7B,kCAAkC,EAAE,CAAC;IACrC,MAAM,EAAE,UAAU,EAAE,GAAG,eAAe,EAAE,GAAG,OAAO,CAAC;IACnD,MAAM,UAAU,GAAG,kBAAkB,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC;IAEtD,OAAO,kBAAkB,CAAC;QACtB,IAAI,EAAE,aAAa;QACnB,UAAU;QACV,MAAM,CAAC,MAAM;YACT,OAAO;gBACH,IAAI,EAAE;oBACF,OAAO,EAAE,IAAI;oBACb,QAAQ,EAAE,CAAC,mBAAmB,EAAE,UAAU,EAAE,wBAAwB,CAAC,eAAe,CAAC,CAAC;oBACtF,WAAW,EAAE,MAAM,CAAC,IAAI,EAAE,WAAW,IAAI,eAAe;oBACxD,WAAW,EAAE,MAAM,CAAC,IAAI,EAAE,WAAW,IAAI,eAAe;oBACxD,IAAI,EAAE,OAAO;oBACb,GAAG,EAAE,mBAAmB;oBACxB,MAAM,EAAE;wBACJ,IAAI,EAAE;4BACF,MAAM,EAAE,gBAAgB;yBAC3B;qBACJ;iBACJ;aACJ,CAAC;QACN,CAAC;KACJ,CAAC,CAAC;AACP,CAAC,CAAC;AAEF,eAAe,IAAI,CAAC;AACpB,OAAO,EAA2C,MAAM,uBAAuB,CAAC;AAChF,OAAO,EAAsB,CAAC","sourcesContent":["import type { Plugin } from \"vitest/config\";\nimport { assertSupportedNodeVersion, createConfigLoader } from \"@gtkx/config/internal\";\nimport createConfigPlugin from \"@gtkx/config/vite-plugin\";\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\nimport { type HeadlessOptions, STATIC_HEADLESS_ENV } from \"./headless-display.ts\";\nimport { reapStaleHeadlessDisplaysAtStartup } from \"./reap-headless-displays.ts\";\n\n/**\n * Options accepted by the GTKX Vitest plugin. Every headless display\n * setting is optional and falls back to a built-in default when omitted.\n */\ntype PluginOptions = Partial<HeadlessOptions> & Partial<Record<\"configFile\", string | undefined>>;\n\nconst GTKX_INLINE_DEPS: RegExp[] = [/@gtkx\\/(?!native)/, /[/\\\\]\\.gtkx[/\\\\]/];\nconst DEFAULT_TIMEOUT = 30_000;\n\nconst workerPreloadUrl = (): URL => {\n const sibling = join(import.meta.dirname, \"worker-preload.js\");\n const path = existsSync(sibling) ? sibling : join(import.meta.dirname, \"..\", \"dist\", \"worker-preload.js\");\n\n return pathToFileURL(path);\n};\n\nconst headlessPreloadSpecifier = (options: Partial<HeadlessOptions>): string => {\n const url = workerPreloadUrl();\n\n for (const [key, value] of Object.entries(options)) {\n url.searchParams.set(key, value);\n }\n\n return url.href;\n};\n\n/**\n * Vitest plugin that runs each test worker against its own isolated headless\n * Wayland display. It configures the forks pool, injects the worker preload and\n * setup files, and sets the environment needed for headless GTK4 rendering.\n *\n * Each worker's compositor, session bus, and private runtime directory are torn\n * down when the worker exits, and a guard process tears them down as well when\n * the worker or the Vitest process that launched it is killed with `SIGKILL`.\n * Creating the plugin reaps stale `gtkx-xdg-*` runtime directories left behind\n * by earlier runs, which `gtkx cleanup` also does on demand.\n *\n * @param options Headless display settings (size, compositor) forwarded to each worker.\n * @returns A Vitest config plugin.\n */\nconst gtkx = (options: PluginOptions = {}): Plugin => {\n assertSupportedNodeVersion();\n reapStaleHeadlessDisplaysAtStartup();\n const { configFile, ...headlessOptions } = options;\n const loadConfig = createConfigLoader({ configFile });\n\n return createConfigPlugin({\n name: \"gtkx:vitest\",\n loadConfig,\n config(config) {\n return {\n test: {\n globals: true,\n execArgv: [\"--disable-sigusr1\", \"--import\", headlessPreloadSpecifier(headlessOptions)],\n testTimeout: config.test?.testTimeout ?? DEFAULT_TIMEOUT,\n hookTimeout: config.test?.hookTimeout ?? DEFAULT_TIMEOUT,\n pool: \"forks\",\n env: STATIC_HEADLESS_ENV,\n server: {\n deps: {\n inline: GTKX_INLINE_DEPS,\n },\n },\n },\n };\n },\n });\n};\n\nexport default gtkx;\nexport { type CompositorId, type HeadlessOptions } from \"./headless-display.ts\";\nexport { type PluginOptions };\n"]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type CleanupDirectoryIdentity } from "@gtkx/utils";
|
|
2
|
+
type StaleHeadlessDisplay = {
|
|
3
|
+
runtimeDir: string;
|
|
4
|
+
cleanupDirectory: CleanupDirectoryIdentity;
|
|
5
|
+
};
|
|
6
|
+
declare const findStaleHeadlessDisplays: () => StaleHeadlessDisplay[];
|
|
7
|
+
declare const reapStaleHeadlessDisplays: (candidates?: readonly StaleHeadlessDisplay[]) => string[];
|
|
8
|
+
declare const reapStaleHeadlessDisplaysAtStartup: () => void;
|
|
9
|
+
export { findStaleHeadlessDisplays, reapStaleHeadlessDisplays, reapStaleHeadlessDisplaysAtStartup, type StaleHeadlessDisplay, };
|
|
10
|
+
//# sourceMappingURL=reap-headless-displays.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reap-headless-displays.d.ts","sourceRoot":"","sources":["../src/reap-headless-displays.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,KAAK,wBAAwB,EAIhC,MAAM,aAAa,CAAC;AAWrB,KAAK,oBAAoB,GAAG;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,wBAAwB,CAAC;CAC9C,CAAC;AA8MF,QAAA,MAAM,yBAAyB,QAAO,oBAAoB,EAYzD,CAAC;AAkCF,QAAA,MAAM,yBAAyB,GAC3B,aAAY,SAAS,oBAAoB,EAAgC,KAC1E,MAAM,EAiBR,CAAC;AAEF,QAAA,MAAM,kCAAkC,QAAO,IAS9C,CAAC;AAEF,OAAO,EACH,yBAAyB,EACzB,yBAAyB,EACzB,kCAAkC,EAClC,KAAK,oBAAoB,GAC5B,CAAC"}
|