@gtkx/vitest 1.0.0-rc.1 → 1.0.0-rc.2
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-display.d.ts +8 -7
- package/dist/headless-display.d.ts.map +1 -1
- package/dist/headless-display.js +221 -136
- package/dist/headless-display.js.map +1 -1
- package/dist/headless-globals.d.ts +10 -0
- package/dist/headless-globals.d.ts.map +1 -0
- package/dist/headless-globals.js +13 -0
- package/dist/headless-globals.js.map +1 -0
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -6
- package/dist/index.js.map +1 -1
- package/dist/install-headless-shutdown.d.ts +3 -0
- package/dist/install-headless-shutdown.d.ts.map +1 -0
- package/dist/install-headless-shutdown.js +12 -0
- package/dist/install-headless-shutdown.js.map +1 -0
- package/dist/notification-service.d.ts +2 -1
- package/dist/notification-service.d.ts.map +1 -1
- package/dist/notification-service.js +17 -16
- package/dist/notification-service.js.map +1 -1
- package/dist/resolve-executable.d.ts +3 -0
- package/dist/resolve-executable.d.ts.map +1 -0
- package/dist/resolve-executable.js +26 -0
- package/dist/resolve-executable.js.map +1 -0
- package/dist/virtual-seat.d.ts +3 -0
- package/dist/virtual-seat.d.ts.map +1 -0
- package/dist/virtual-seat.js +155 -0
- package/dist/virtual-seat.js.map +1 -0
- package/dist/worker-preload.js +2 -1
- package/dist/worker-preload.js.map +1 -1
- package/dist/worker-setup.d.ts +1 -5
- package/dist/worker-setup.d.ts.map +1 -1
- package/dist/worker-setup.js +1 -8
- package/dist/worker-setup.js.map +1 -1
- package/package.json +4 -6
- package/src/dbus-native.d.ts +4 -4
- package/src/headless-display.ts +351 -174
- package/src/headless-globals.ts +22 -0
- package/src/index.ts +15 -12
- package/src/install-headless-shutdown.ts +15 -0
- package/src/notification-service.ts +22 -19
- package/src/resolve-executable.ts +32 -0
- package/src/virtual-seat.ts +214 -0
- package/src/worker-preload.ts +2 -1
- package/src/worker-setup.ts +1 -13
|
@@ -1,20 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Wayland compositors that can back a headless display.
|
|
3
3
|
*/
|
|
4
|
-
|
|
5
|
-
export declare const DEFAULT_HEADLESS_SIZE = "1024x768";
|
|
4
|
+
type CompositorId = "sway" | "weston";
|
|
6
5
|
/**
|
|
7
6
|
* Settings for the per-worker headless Wayland display.
|
|
8
7
|
*/
|
|
9
|
-
|
|
8
|
+
type HeadlessOptions = {
|
|
10
9
|
/** Output resolution as a "WIDTHxHEIGHT" string, for example "1024x768". */
|
|
11
10
|
size: string;
|
|
12
11
|
/** Wayland compositor used to back the headless display. */
|
|
13
12
|
compositor: CompositorId;
|
|
14
13
|
};
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
export declare const STATIC_HEADLESS_ENV: {
|
|
14
|
+
declare const DEFAULT_HEADLESS_SIZE = "1024x768";
|
|
15
|
+
declare const STATIC_HEADLESS_ENV: {
|
|
18
16
|
GDK_BACKEND: string;
|
|
19
17
|
GDK_DISABLE: string;
|
|
20
18
|
GDK_DEBUG: string;
|
|
@@ -26,5 +24,8 @@ export declare const STATIC_HEADLESS_ENV: {
|
|
|
26
24
|
ALSOFT_DRIVERS: string;
|
|
27
25
|
ALSOFT_LOGLEVEL: string;
|
|
28
26
|
};
|
|
29
|
-
|
|
27
|
+
declare const resolveHeadlessOptions: (provided: Partial<HeadlessOptions>) => HeadlessOptions;
|
|
28
|
+
declare const readHeadlessOptions: (params: URLSearchParams) => Partial<HeadlessOptions>;
|
|
29
|
+
declare const startHeadlessDisplay: (options: HeadlessOptions) => Promise<() => void>;
|
|
30
|
+
export { DEFAULT_HEADLESS_SIZE, STATIC_HEADLESS_ENV, resolveHeadlessOptions, readHeadlessOptions, startHeadlessDisplay, type CompositorId, type HeadlessOptions, };
|
|
30
31
|
//# sourceMappingURL=headless-display.d.ts.map
|
|
@@ -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":"AASA;;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;AAgDF,QAAA,MAAM,qBAAqB,aAAa,CAAC;AA6DzC,QAAA,MAAM,mBAAmB;;;;;;;;;;;CAWxB,CAAC;AAEF,QAAA,MAAM,sBAAsB,GAAI,UAAU,OAAO,CAAC,eAAe,CAAC,KAAG,eAGnE,CAAC;AAiDH,QAAA,MAAM,mBAAmB,GAAI,QAAQ,eAAe,KAAG,OAAO,CAAC,eAAe,CAe7E,CAAC;AA4OF,QAAA,MAAM,oBAAoB,GAAU,SAAS,eAAe,KAAG,OAAO,CAAC,MAAM,IAAI,CAiDhF,CAAC;AAEF,OAAO,EACH,qBAAqB,EACrB,mBAAmB,EACnB,sBAAsB,EACtB,mBAAmB,EACnB,oBAAoB,EACpB,KAAK,YAAY,EACjB,KAAK,eAAe,GACvB,CAAC"}
|
package/dist/headless-display.js
CHANGED
|
@@ -4,43 +4,18 @@ import { Socket } from "node:net";
|
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
5
|
import { join } from "node:path";
|
|
6
6
|
import { startNotificationService } from "./notification-service.js";
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
if (!(name in snapshot))
|
|
16
|
-
snapshot[name] = process.env[name];
|
|
17
|
-
process.env[name] = value;
|
|
18
|
-
}
|
|
19
|
-
};
|
|
20
|
-
const restoreEnv = (snapshot) => {
|
|
21
|
-
for (const [name, previous] of Object.entries(snapshot)) {
|
|
22
|
-
if (previous === undefined)
|
|
23
|
-
delete process.env[name];
|
|
24
|
-
else
|
|
25
|
-
process.env[name] = previous;
|
|
26
|
-
}
|
|
27
|
-
};
|
|
28
|
-
const spawnWithParentDeathSignal = (command, args, stdio) => {
|
|
29
|
-
const child = spawn("setpriv", ["--pdeathsig", "SIGKILL", command, ...args], { stdio });
|
|
30
|
-
child.unref();
|
|
31
|
-
return child;
|
|
32
|
-
};
|
|
33
|
-
let westonFakeSeatSupport;
|
|
34
|
-
const westonSupportsFakeSeat = () => {
|
|
35
|
-
if (westonFakeSeatSupport === undefined) {
|
|
36
|
-
const help = spawnSync("weston", ["--help"], { encoding: "utf8" });
|
|
37
|
-
westonFakeSeatSupport = `${help.stdout}${help.stderr}`.includes("--fake-seat");
|
|
38
|
-
}
|
|
39
|
-
return westonFakeSeatSupport;
|
|
40
|
-
};
|
|
7
|
+
import { resolveExecutable } from "./resolve-executable.js";
|
|
8
|
+
import { startVirtualSeat } from "./virtual-seat.js";
|
|
9
|
+
const DEFAULT_HEADLESS_SIZE = "1024x768";
|
|
10
|
+
const DEFAULT_HEADLESS_COMPOSITOR = "sway";
|
|
11
|
+
const BUS_CONFIG_DOCTYPE = '<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN" ' +
|
|
12
|
+
'"https://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">';
|
|
13
|
+
const PARENT_DEATH_SCRIPT = 'trap \'kill -9 "$child" 2>/dev/null\' TERM; "$@" & child=$!; wait "$child"';
|
|
14
|
+
const hasWestonFakeSeat = createWestonFakeSeatProbe();
|
|
41
15
|
const compositorRegistry = {
|
|
42
16
|
sway: {
|
|
43
17
|
socket: "wayland-1",
|
|
18
|
+
needsVirtualSeat: true,
|
|
44
19
|
env: {
|
|
45
20
|
WLR_BACKENDS: "headless",
|
|
46
21
|
WLR_RENDERER: "pixman",
|
|
@@ -65,119 +40,207 @@ const compositorRegistry = {
|
|
|
65
40
|
},
|
|
66
41
|
weston: {
|
|
67
42
|
socket: "wayland-0",
|
|
43
|
+
needsVirtualSeat: false,
|
|
68
44
|
env: {},
|
|
69
45
|
start: (_runtimeDir, width, height) => spawnWithParentDeathSignal("weston", [
|
|
70
46
|
"--backend=headless",
|
|
71
47
|
"--renderer=pixman",
|
|
72
|
-
...(
|
|
48
|
+
...(hasWestonFakeSeat() ? ["--fake-seat"] : []),
|
|
73
49
|
`--width=${width}`,
|
|
74
50
|
`--height=${height}`,
|
|
75
51
|
"--socket=wayland-0",
|
|
76
52
|
], ["ignore", "ignore", "pipe"]),
|
|
77
53
|
},
|
|
78
54
|
};
|
|
55
|
+
const STATIC_HEADLESS_ENV = {
|
|
56
|
+
GDK_BACKEND: "wayland",
|
|
57
|
+
GDK_DISABLE: "vulkan",
|
|
58
|
+
GDK_DEBUG: "no-vsync",
|
|
59
|
+
GSK_RENDERER: "cairo",
|
|
60
|
+
GTK_A11Y: "test",
|
|
61
|
+
LIBGL_ALWAYS_SOFTWARE: "1",
|
|
62
|
+
GST_GL_WINDOW: "none",
|
|
63
|
+
GSETTINGS_BACKEND: "memory",
|
|
64
|
+
ALSOFT_DRIVERS: "null",
|
|
65
|
+
ALSOFT_LOGLEVEL: "0",
|
|
66
|
+
};
|
|
67
|
+
const resolveHeadlessOptions = (provided) => ({
|
|
68
|
+
size: provided.size ?? DEFAULT_HEADLESS_SIZE,
|
|
69
|
+
compositor: provided.compositor ?? DEFAULT_HEADLESS_COMPOSITOR,
|
|
70
|
+
});
|
|
71
|
+
const applyEnv = (snapshot, values) => {
|
|
72
|
+
for (const [name, value] of Object.entries(values)) {
|
|
73
|
+
if (!Object.hasOwn(snapshot, name)) {
|
|
74
|
+
snapshot[name] = process.env[name];
|
|
75
|
+
}
|
|
76
|
+
process.env[name] = value;
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
const restoreEnv = (snapshot) => {
|
|
80
|
+
for (const [name, previous] of Object.entries(snapshot)) {
|
|
81
|
+
if (previous === undefined) {
|
|
82
|
+
Reflect.deleteProperty(process.env, name);
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
process.env[name] = previous;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
const spawnWithParentDeathSignal = (command, args, stdio) => {
|
|
90
|
+
const child = spawn(resolveExecutable("setpriv"), ["--pdeathsig", "SIGTERM", "sh", "-c", PARENT_DEATH_SCRIPT, "sh", command, ...args], { stdio });
|
|
91
|
+
child.unref();
|
|
92
|
+
return child;
|
|
93
|
+
};
|
|
94
|
+
function createWestonFakeSeatProbe() {
|
|
95
|
+
let isSupported;
|
|
96
|
+
return () => {
|
|
97
|
+
if (isSupported === undefined) {
|
|
98
|
+
const help = spawnSync(resolveExecutable("weston"), ["--help"], { encoding: "utf8" });
|
|
99
|
+
isSupported = `${help.stdout}${help.stderr}`.includes("--fake-seat");
|
|
100
|
+
}
|
|
101
|
+
return isSupported;
|
|
102
|
+
};
|
|
103
|
+
}
|
|
79
104
|
const isCompositorId = (value) => Object.hasOwn(compositorRegistry, value);
|
|
80
|
-
|
|
105
|
+
const readHeadlessOptions = (params) => {
|
|
81
106
|
const options = {};
|
|
82
107
|
const size = params.get("size");
|
|
83
|
-
if (size !== null)
|
|
108
|
+
if (size !== null) {
|
|
84
109
|
options.size = size;
|
|
110
|
+
}
|
|
85
111
|
const compositor = params.get("compositor");
|
|
86
|
-
if (compositor !== null && isCompositorId(compositor))
|
|
112
|
+
if (compositor !== null && isCompositorId(compositor)) {
|
|
87
113
|
options.compositor = compositor;
|
|
114
|
+
}
|
|
88
115
|
return options;
|
|
89
116
|
};
|
|
90
117
|
const startCompositor = (runtimeDir, options, env) => {
|
|
91
118
|
const descriptor = compositorRegistry[options.compositor];
|
|
92
|
-
const [width = "", height = ""] = options.size.split("x");
|
|
119
|
+
const [width = "", height = ""] = options.size.split("x", 2);
|
|
93
120
|
applyEnv(env, descriptor.env);
|
|
94
|
-
return {
|
|
121
|
+
return {
|
|
122
|
+
child: descriptor.start(runtimeDir, width, height),
|
|
123
|
+
socket: descriptor.socket,
|
|
124
|
+
needsVirtualSeat: descriptor.needsVirtualSeat,
|
|
125
|
+
};
|
|
95
126
|
};
|
|
127
|
+
const noVirtualSeat = () => undefined;
|
|
128
|
+
const attachVirtualSeat = (compositor, socketPath) => compositor.needsVirtualSeat ? startVirtualSeat(socketPath) : Promise.resolve(noVirtualSeat);
|
|
96
129
|
const writeBusConfig = (busConfigPath, busSocketPath) => {
|
|
97
|
-
writeFileSync(busConfigPath,
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
<
|
|
101
|
-
<
|
|
102
|
-
<
|
|
103
|
-
|
|
104
|
-
<allow eavesdrop="true"/>
|
|
105
|
-
<allow
|
|
106
|
-
|
|
107
|
-
</
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
130
|
+
writeFileSync(busConfigPath, [
|
|
131
|
+
BUS_CONFIG_DOCTYPE,
|
|
132
|
+
"<busconfig>",
|
|
133
|
+
" <type>session</type>",
|
|
134
|
+
` <listen>unix:path=${busSocketPath}</listen>`,
|
|
135
|
+
" <auth>EXTERNAL</auth>",
|
|
136
|
+
' <policy context="default">',
|
|
137
|
+
' <allow send_destination="*" eavesdrop="true"/>',
|
|
138
|
+
' <allow eavesdrop="true"/>',
|
|
139
|
+
' <allow own="*"/>',
|
|
140
|
+
" </policy>",
|
|
141
|
+
"</busconfig>",
|
|
142
|
+
].join("\n"));
|
|
143
|
+
};
|
|
144
|
+
const captureStderr = (child) => {
|
|
111
145
|
const stderr = child?.stderr ?? null;
|
|
146
|
+
let log = "";
|
|
112
147
|
stderr?.setEncoding("utf8");
|
|
113
148
|
stderr?.on("data", (chunk) => {
|
|
114
149
|
log += chunk;
|
|
115
150
|
});
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
151
|
+
return {
|
|
152
|
+
read: () => log,
|
|
153
|
+
stop: () => {
|
|
154
|
+
stderr?.removeAllListeners("data");
|
|
155
|
+
stderr?.resume();
|
|
156
|
+
if (stderr instanceof Socket) {
|
|
157
|
+
stderr.unref();
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
};
|
|
162
|
+
const trackChild = (child, handlers) => {
|
|
163
|
+
child.on("exit", handlers.exit);
|
|
164
|
+
child.on("error", handlers.error);
|
|
165
|
+
return () => {
|
|
166
|
+
child.removeListener("exit", handlers.exit);
|
|
167
|
+
child.removeListener("error", handlers.error);
|
|
168
|
+
};
|
|
169
|
+
};
|
|
170
|
+
const exitedMessage = (label, path, code, signal) => `${label} exited (code ${String(code)}, signal ${signal ?? "null"}) before ${path} appeared`;
|
|
171
|
+
const runCleanups = (cleanups) => {
|
|
172
|
+
for (const cleanup of cleanups) {
|
|
173
|
+
cleanup();
|
|
174
|
+
}
|
|
175
|
+
cleanups.length = 0;
|
|
176
|
+
};
|
|
177
|
+
const pollForPath = (path, onFound) => setInterval(() => {
|
|
178
|
+
if (existsSync(path)) {
|
|
179
|
+
onFound();
|
|
180
|
+
}
|
|
181
|
+
}, 50);
|
|
182
|
+
const stderrSuffix = (child, stderr) => child ? `\n${stderr.read()}` : "";
|
|
183
|
+
const hasAlreadyAborted = (signal, onAbort, cleanups) => {
|
|
184
|
+
if (signal === undefined) {
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
if (signal.aborted) {
|
|
188
|
+
return true;
|
|
189
|
+
}
|
|
190
|
+
signal.addEventListener("abort", onAbort);
|
|
191
|
+
cleanups.push(() => {
|
|
192
|
+
signal.removeEventListener("abort", onAbort);
|
|
193
|
+
});
|
|
194
|
+
return false;
|
|
195
|
+
};
|
|
196
|
+
const watchForSocket = ({ path, options, resolve, reject }) => {
|
|
197
|
+
const { label, timeout = 15_000, child, signal } = options;
|
|
198
|
+
const stderr = captureStderr(child);
|
|
199
|
+
const cleanups = [stderr.stop];
|
|
200
|
+
const stop = () => {
|
|
201
|
+
runCleanups(cleanups);
|
|
202
|
+
};
|
|
203
|
+
const fail = (message) => {
|
|
204
|
+
stop();
|
|
205
|
+
reject(new Error(message));
|
|
128
206
|
};
|
|
129
207
|
const onExit = (code, terminationSignal) => {
|
|
130
|
-
|
|
131
|
-
reject(new Error(`${label} exited (code ${code ?? "null"}, signal ${terminationSignal ?? "null"}) before ${path} appeared\n${log}`));
|
|
208
|
+
fail(`${exitedMessage(label, path, code, terminationSignal)}\n${stderr.read()}`);
|
|
132
209
|
};
|
|
133
210
|
const onError = (cause) => {
|
|
134
|
-
|
|
135
|
-
reject(new Error(`${label} failed to spawn: ${cause.message}\n${log}`));
|
|
211
|
+
fail(`${label} failed to spawn: ${cause.message}\n${stderr.read()}`);
|
|
136
212
|
};
|
|
137
213
|
const onAbort = () => {
|
|
138
|
-
|
|
139
|
-
reject(new Error(`${label} startup aborted before ${path} appeared`));
|
|
214
|
+
fail(`${label} startup aborted before ${path} appeared`);
|
|
140
215
|
};
|
|
141
|
-
poll =
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
timer = setTimeout(() => {
|
|
148
|
-
stopListening();
|
|
149
|
-
const suffix = child ? `\n${log}` : "";
|
|
150
|
-
reject(new Error(`${label} did not become available within ${timeout}ms${suffix}`));
|
|
216
|
+
const poll = pollForPath(path, () => {
|
|
217
|
+
stop();
|
|
218
|
+
resolve();
|
|
219
|
+
});
|
|
220
|
+
const timer = setTimeout(() => {
|
|
221
|
+
fail(`${label} did not become available within ${String(timeout)}ms${stderrSuffix(child, stderr)}`);
|
|
151
222
|
}, timeout);
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
223
|
+
cleanups.push(() => {
|
|
224
|
+
clearInterval(poll);
|
|
225
|
+
clearTimeout(timer);
|
|
226
|
+
});
|
|
227
|
+
if (child) {
|
|
228
|
+
cleanups.push(trackChild(child, { exit: onExit, error: onError }));
|
|
229
|
+
}
|
|
230
|
+
if (hasAlreadyAborted(signal, onAbort, cleanups)) {
|
|
231
|
+
onAbort();
|
|
160
232
|
}
|
|
161
|
-
});
|
|
162
|
-
export const STATIC_HEADLESS_ENV = {
|
|
163
|
-
GDK_BACKEND: "wayland",
|
|
164
|
-
GDK_DISABLE: "vulkan",
|
|
165
|
-
GDK_DEBUG: "no-vsync",
|
|
166
|
-
GSK_RENDERER: "cairo",
|
|
167
|
-
GTK_A11Y: "test",
|
|
168
|
-
LIBGL_ALWAYS_SOFTWARE: "1",
|
|
169
|
-
GST_GL_WINDOW: "none",
|
|
170
|
-
GSETTINGS_BACKEND: "memory",
|
|
171
|
-
ALSOFT_DRIVERS: "null",
|
|
172
|
-
ALSOFT_LOGLEVEL: "0",
|
|
173
233
|
};
|
|
234
|
+
const waitForSocket = (path, options) => new Promise((resolve, reject) => {
|
|
235
|
+
watchForSocket({ path, options, resolve, reject });
|
|
236
|
+
});
|
|
174
237
|
const captureCompositorStderr = (child, logPath) => {
|
|
175
238
|
const captured = [];
|
|
176
239
|
const stderr = child.stderr;
|
|
177
240
|
if (stderr !== null) {
|
|
178
241
|
stderr.setEncoding("utf8");
|
|
179
242
|
const logStream = createWriteStream(logPath);
|
|
180
|
-
logStream.on("error", () =>
|
|
243
|
+
logStream.on("error", () => undefined);
|
|
181
244
|
stderr.on("data", (chunk) => {
|
|
182
245
|
captured.push(chunk);
|
|
183
246
|
logStream.write(chunk);
|
|
@@ -185,24 +248,48 @@ const captureCompositorStderr = (child, logPath) => {
|
|
|
185
248
|
}
|
|
186
249
|
return captured;
|
|
187
250
|
};
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
251
|
+
const compositorExitMessage = (code, signal, capturedStderr) => `[gtkx] the headless compositor exited (code ${String(code)}, signal ${signal ?? "null"}); ` +
|
|
252
|
+
`every Wayland client in this worker has been severed.\n${capturedStderr.join("")}`;
|
|
253
|
+
const watchCompositorExit = (child, capturedStderr) => {
|
|
254
|
+
const report = (code, signal) => {
|
|
255
|
+
process.stderr.write(compositorExitMessage(code, signal, capturedStderr));
|
|
256
|
+
};
|
|
257
|
+
child.on("exit", report);
|
|
258
|
+
return () => child.removeListener("exit", report);
|
|
259
|
+
};
|
|
260
|
+
const waitForDisplaySockets = async (sockets) => {
|
|
261
|
+
const { compositor, compositorSocketPath, busChild, busSocketPath } = sockets;
|
|
262
|
+
const abort = new AbortController();
|
|
263
|
+
try {
|
|
264
|
+
await Promise.all([
|
|
265
|
+
waitForSocket(compositorSocketPath, {
|
|
266
|
+
label: "Compositor",
|
|
267
|
+
child: compositor.child,
|
|
268
|
+
signal: abort.signal,
|
|
269
|
+
}),
|
|
270
|
+
waitForSocket(busSocketPath, { label: "D-Bus session bus", child: busChild, signal: abort.signal }),
|
|
271
|
+
]);
|
|
272
|
+
}
|
|
273
|
+
finally {
|
|
274
|
+
abort.abort();
|
|
275
|
+
}
|
|
193
276
|
};
|
|
194
|
-
const
|
|
195
|
-
|
|
277
|
+
const killSpawned = (children) => {
|
|
278
|
+
for (const child of children) {
|
|
279
|
+
child.kill("SIGTERM");
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
const makeTeardown = (stops) => {
|
|
283
|
+
let isTorndown = false;
|
|
196
284
|
return () => {
|
|
197
|
-
if (
|
|
285
|
+
if (isTorndown) {
|
|
198
286
|
return;
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
removeRuntime();
|
|
287
|
+
}
|
|
288
|
+
isTorndown = true;
|
|
289
|
+
runCleanups(stops);
|
|
203
290
|
};
|
|
204
291
|
};
|
|
205
|
-
|
|
292
|
+
const startHeadlessDisplay = async (options) => {
|
|
206
293
|
const env = {};
|
|
207
294
|
const runtimeDir = mkdtempSync(join(tmpdir(), "gtkx-xdg-"));
|
|
208
295
|
chmodSync(runtimeDir, 0o700);
|
|
@@ -222,29 +309,27 @@ export const startHeadlessDisplay = async (options) => {
|
|
|
222
309
|
const compositor = startCompositor(runtimeDir, options, env);
|
|
223
310
|
spawned.push(compositor.child);
|
|
224
311
|
applyEnv(env, { WAYLAND_DISPLAY: compositor.socket });
|
|
225
|
-
const
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
waitForSocket(join(runtimeDir, compositor.socket), {
|
|
229
|
-
label: "Compositor",
|
|
230
|
-
child: compositor.child,
|
|
231
|
-
signal: abort.signal,
|
|
232
|
-
}),
|
|
233
|
-
waitForSocket(busSocketPath, { label: "D-Bus session bus", child: busChild, signal: abort.signal }),
|
|
234
|
-
]);
|
|
235
|
-
}
|
|
236
|
-
finally {
|
|
237
|
-
abort.abort();
|
|
238
|
-
}
|
|
312
|
+
const compositorSocketPath = join(runtimeDir, compositor.socket);
|
|
313
|
+
await waitForDisplaySockets({ compositor, compositorSocketPath, busChild, busSocketPath });
|
|
314
|
+
const stopVirtualSeat = await attachVirtualSeat(compositor, compositorSocketPath);
|
|
239
315
|
const stopNotifications = await startNotificationService(`unix:path=${busSocketPath}`);
|
|
240
|
-
const capturedStderr = captureCompositorStderr(compositor.child, join(runtimeDir, "
|
|
241
|
-
|
|
316
|
+
const capturedStderr = captureCompositorStderr(compositor.child, join(runtimeDir, "compositor.stderr.log"));
|
|
317
|
+
const stopExitWatch = watchCompositorExit(compositor.child, capturedStderr);
|
|
318
|
+
return makeTeardown([
|
|
319
|
+
stopExitWatch,
|
|
320
|
+
() => {
|
|
321
|
+
killSpawned(spawned);
|
|
322
|
+
},
|
|
323
|
+
stopVirtualSeat,
|
|
324
|
+
stopNotifications,
|
|
325
|
+
removeRuntime,
|
|
326
|
+
]);
|
|
242
327
|
}
|
|
243
|
-
catch (
|
|
244
|
-
|
|
245
|
-
child.kill("SIGKILL");
|
|
328
|
+
catch (error) {
|
|
329
|
+
killSpawned(spawned);
|
|
246
330
|
removeRuntime();
|
|
247
|
-
throw
|
|
331
|
+
throw error;
|
|
248
332
|
}
|
|
249
333
|
};
|
|
334
|
+
export { DEFAULT_HEADLESS_SIZE, STATIC_HEADLESS_ENV, resolveHeadlessOptions, readHeadlessOptions, startHeadlessDisplay, };
|
|
250
335
|
//# sourceMappingURL=headless-display.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"headless-display.js","sourceRoot":"","sources":["../src/headless-display.ts"],"names":[],"mappings":"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","sourcesContent":["import { type ChildProcess, type StdioOptions, spawn, spawnSync } from \"node:child_process\";\nimport { chmodSync, createWriteStream, existsSync, mkdtempSync, rmSync, writeFileSync } from \"node:fs\";\nimport { Socket } from \"node:net\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { startNotificationService } from \"./notification-service.js\";\n\n/**\n * Wayland compositors that can back a headless display.\n */\nexport type CompositorId = \"sway\" | \"weston\";\n\nexport const DEFAULT_HEADLESS_SIZE = \"1024x768\";\n\n/**\n * Settings for the per-worker headless Wayland display.\n */\nexport type 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\nconst DEFAULT_HEADLESS_COMPOSITOR: CompositorId = \"weston\";\n\nexport const resolveHeadlessOptions = (provided: Partial<HeadlessOptions>): HeadlessOptions => ({\n size: provided.size ?? DEFAULT_HEADLESS_SIZE,\n compositor: provided.compositor ?? DEFAULT_HEADLESS_COMPOSITOR,\n});\n\ntype EnvSnapshot = { [name: string]: string | undefined };\n\nconst applyEnv = (snapshot: EnvSnapshot, values: { [name: string]: string }): void => {\n for (const [name, value] of Object.entries(values)) {\n if (!(name in snapshot)) snapshot[name] = process.env[name];\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) delete process.env[name];\n else process.env[name] = previous;\n }\n};\n\nconst spawnWithParentDeathSignal = (command: string, args: string[], stdio: StdioOptions): ChildProcess => {\n const child = spawn(\"setpriv\", [\"--pdeathsig\", \"SIGKILL\", command, ...args], { stdio });\n child.unref();\n return child;\n};\n\nlet westonFakeSeatSupport: boolean | undefined;\n\nconst westonSupportsFakeSeat = (): boolean => {\n if (westonFakeSeatSupport === undefined) {\n const help = spawnSync(\"weston\", [\"--help\"], { encoding: \"utf8\" });\n westonFakeSeatSupport = `${help.stdout}${help.stderr}`.includes(\"--fake-seat\");\n }\n return westonFakeSeatSupport;\n};\n\ntype CompositorDescriptor = {\n socket: string;\n env: { [name: string]: string };\n start: (runtimeDir: string, width: string, height: string) => ChildProcess;\n};\n\nconst compositorRegistry: { [K in CompositorId]: CompositorDescriptor } = {\n sway: {\n socket: \"wayland-1\",\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 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 return spawnWithParentDeathSignal(\"sway\", [\"-c\", configPath], [\"ignore\", \"ignore\", \"pipe\"]);\n },\n },\n weston: {\n socket: \"wayland-0\",\n env: {},\n start: (_runtimeDir, width, height) =>\n spawnWithParentDeathSignal(\n \"weston\",\n [\n \"--backend=headless\",\n \"--renderer=pixman\",\n ...(westonSupportsFakeSeat() ? [\"--fake-seat\"] : []),\n `--width=${width}`,\n `--height=${height}`,\n \"--socket=wayland-0\",\n ],\n [\"ignore\", \"ignore\", \"pipe\"],\n ),\n },\n};\n\nconst isCompositorId = (value: string): value is CompositorId => Object.hasOwn(compositorRegistry, value);\n\nexport const readHeadlessOptions = (params: URLSearchParams): Partial<HeadlessOptions> => {\n const options: Partial<HeadlessOptions> = {};\n const size = params.get(\"size\");\n if (size !== null) options.size = size;\n const compositor = params.get(\"compositor\");\n if (compositor !== null && isCompositorId(compositor)) options.compositor = compositor;\n return options;\n};\n\ntype SpawnedCompositor = {\n child: ChildProcess;\n socket: string;\n};\n\nconst startCompositor = (runtimeDir: string, options: HeadlessOptions, env: EnvSnapshot): SpawnedCompositor => {\n const descriptor = compositorRegistry[options.compositor];\n\n const [width = \"\", height = \"\"] = options.size.split(\"x\");\n applyEnv(env, descriptor.env);\n\n return { child: descriptor.start(runtimeDir, width, height), socket: descriptor.socket };\n};\n\nconst writeBusConfig = (busConfigPath: string, busSocketPath: string): void => {\n writeFileSync(\n busConfigPath,\n `<!DOCTYPE busconfig PUBLIC \"-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN\" \"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd\">\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 );\n};\n\ntype WaitForSocketOptions = {\n label: string;\n timeout?: number;\n child?: ChildProcess;\n signal?: AbortSignal;\n};\n\nconst waitForSocket = (path: string, { label, timeout = 15000, child, signal }: WaitForSocketOptions): Promise<void> =>\n new Promise((resolve, reject) => {\n let log = \"\";\n const stderr = child?.stderr ?? null;\n stderr?.setEncoding(\"utf8\");\n stderr?.on(\"data\", (chunk: string) => {\n log += chunk;\n });\n let timer: ReturnType<typeof setTimeout>;\n let poll: ReturnType<typeof setInterval>;\n const stopListening = (): void => {\n clearTimeout(timer);\n clearInterval(poll);\n child?.removeListener(\"exit\", onExit);\n child?.removeListener(\"error\", onError);\n signal?.removeEventListener(\"abort\", onAbort);\n stderr?.removeAllListeners(\"data\");\n stderr?.resume();\n if (stderr instanceof Socket) stderr.unref();\n };\n const onExit = (code: number | null, terminationSignal: NodeJS.Signals | null): void => {\n stopListening();\n reject(\n new Error(\n `${label} exited (code ${code ?? \"null\"}, signal ${terminationSignal ?? \"null\"}) before ${path} appeared\\n${log}`,\n ),\n );\n };\n const onError = (cause: Error): void => {\n stopListening();\n reject(new Error(`${label} failed to spawn: ${cause.message}\\n${log}`));\n };\n const onAbort = (): void => {\n stopListening();\n reject(new Error(`${label} startup aborted before ${path} appeared`));\n };\n poll = setInterval(() => {\n if (existsSync(path)) {\n stopListening();\n resolve();\n }\n }, 50);\n timer = setTimeout(() => {\n stopListening();\n const suffix = child ? `\\n${log}` : \"\";\n reject(new Error(`${label} did not become available within ${timeout}ms${suffix}`));\n }, timeout);\n child?.on(\"exit\", onExit);\n child?.on(\"error\", onError);\n if (signal) {\n if (signal.aborted) {\n onAbort();\n return;\n }\n signal.addEventListener(\"abort\", onAbort);\n }\n });\n\nexport const 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 captureCompositorStderr = (child: ChildProcess, logPath: string): string[] => {\n const captured: string[] = [];\n const stderr = child.stderr;\n if (stderr !== null) {\n stderr.setEncoding(\"utf8\");\n const logStream = createWriteStream(logPath);\n logStream.on(\"error\", () => {});\n stderr.on(\"data\", (chunk: string) => {\n captured.push(chunk);\n logStream.write(chunk);\n });\n }\n return captured;\n};\n\nconst reportUnexpectedCompositorExit = (child: ChildProcess, capturedStderr: string[]): void => {\n if (child.exitCode === null && child.signalCode === null) return;\n process.stderr.write(\n `[gtkx] headless compositor died before teardown (code ${child.exitCode ?? \"null\"}, ` +\n `signal ${child.signalCode ?? \"null\"}); the worker's Wayland client was severed.\\n${capturedStderr.join(\"\")}`,\n );\n};\n\nconst makeTeardown = (\n compositor: ChildProcess,\n capturedStderr: string[],\n stopNotifications: () => void,\n removeRuntime: () => void,\n): (() => void) => {\n let torndown = false;\n return (): void => {\n if (torndown) return;\n torndown = true;\n reportUnexpectedCompositorExit(compositor, capturedStderr);\n stopNotifications();\n removeRuntime();\n };\n};\n\nexport const 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, { XDG_RUNTIME_DIR: runtimeDir });\n\n const busConfigPath = join(runtimeDir, \"session.conf\");\n const busSocketPath = join(runtimeDir, \"bus\");\n writeBusConfig(busConfigPath, busSocketPath);\n\n const busChild = spawnWithParentDeathSignal(\n \"dbus-daemon\",\n [`--config-file=${busConfigPath}`],\n [\"ignore\", \"ignore\", \"pipe\"],\n );\n spawned.push(busChild);\n applyEnv(env, { DBUS_SESSION_BUS_ADDRESS: `unix:path=${busSocketPath}` });\n\n const compositor = startCompositor(runtimeDir, options, env);\n spawned.push(compositor.child);\n\n applyEnv(env, { WAYLAND_DISPLAY: compositor.socket });\n\n const abort = new AbortController();\n try {\n await Promise.all([\n waitForSocket(join(runtimeDir, compositor.socket), {\n label: \"Compositor\",\n child: compositor.child,\n signal: abort.signal,\n }),\n waitForSocket(busSocketPath, { label: \"D-Bus session bus\", child: busChild, signal: abort.signal }),\n ]);\n } finally {\n abort.abort();\n }\n\n const stopNotifications = await startNotificationService(`unix:path=${busSocketPath}`);\n\n const capturedStderr = captureCompositorStderr(compositor.child, join(runtimeDir, \"weston.stderr.log\"));\n return makeTeardown(compositor.child, capturedStderr, stopNotifications, removeRuntime);\n } catch (cause) {\n for (const child of spawned) child.kill(\"SIGKILL\");\n removeRuntime();\n throw cause;\n }\n};\n"]}
|
|
1
|
+
{"version":3,"file":"headless-display.js","sourceRoot":"","sources":["../src/headless-display.ts"],"names":[],"mappings":"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","sourcesContent":["import { type ChildProcess, spawn, spawnSync, type StdioOptions } from \"node:child_process\";\nimport { chmodSync, createWriteStream, existsSync, mkdtempSync, rmSync, writeFileSync } from \"node:fs\";\nimport { Socket } from \"node:net\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { startNotificationService } from \"./notification-service.js\";\nimport { resolveExecutable } from \"./resolve-executable.js\";\nimport { startVirtualSeat } from \"./virtual-seat.js\";\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 needsVirtualSeat: boolean;\n start: (runtimeDir: string, width: string, height: string) => ChildProcess;\n};\n\ntype SpawnedCompositor = {\n child: ChildProcess;\n socket: string;\n needsVirtualSeat: boolean;\n};\n\ntype WaitForSocketOptions = {\n label: string;\n timeout?: number;\n child?: ChildProcess;\n signal?: AbortSignal;\n};\n\ntype StderrCapture = {\n read: () => string;\n stop: () => void;\n};\n\ntype ChildHandlers = {\n exit: (code: number | null, signal: NodeJS.Signals | null) => void;\n error: (cause: Error) => void;\n};\n\ntype DisplaySockets = {\n compositor: SpawnedCompositor;\n compositorSocketPath: string;\n busChild: ChildProcess;\n busSocketPath: string;\n};\n\ntype SocketWatch = {\n path: string;\n options: WaitForSocketOptions;\n resolve: () => void;\n reject: (error: Error) => void;\n};\n\nconst DEFAULT_HEADLESS_SIZE = \"1024x768\";\nconst DEFAULT_HEADLESS_COMPOSITOR: CompositorId = \"sway\";\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 PARENT_DEATH_SCRIPT = 'trap \\'kill -9 \"$child\" 2>/dev/null\\' TERM; \"$@\" & child=$!; wait \"$child\"';\nconst hasWestonFakeSeat = createWestonFakeSeatProbe();\n\nconst compositorRegistry: Record<CompositorId, CompositorDescriptor> = {\n sway: {\n socket: \"wayland-1\",\n needsVirtualSeat: 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 spawnWithParentDeathSignal(\"sway\", [\"-c\", configPath], [\"ignore\", \"ignore\", \"pipe\"]);\n },\n },\n weston: {\n socket: \"wayland-0\",\n needsVirtualSeat: 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 [\"ignore\", \"ignore\", \"pipe\"],\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 size: provided.size ?? DEFAULT_HEADLESS_SIZE,\n compositor: provided.compositor ?? DEFAULT_HEADLESS_COMPOSITOR,\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\nconst spawnWithParentDeathSignal = (command: string, args: string[], stdio: StdioOptions): ChildProcess => {\n const child = spawn(\n resolveExecutable(\"setpriv\"),\n [\"--pdeathsig\", \"SIGTERM\", \"sh\", \"-c\", PARENT_DEATH_SCRIPT, \"sh\", command, ...args],\n { stdio },\n );\n\n child.unref();\n\n return child;\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 needsVirtualSeat: descriptor.needsVirtualSeat,\n };\n};\n\nconst noVirtualSeat = (): void => undefined;\n\nconst attachVirtualSeat = (compositor: SpawnedCompositor, socketPath: string): Promise<() => void> =>\n compositor.needsVirtualSeat ? 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 captureStderr = (child: ChildProcess | undefined): StderrCapture => {\n const stderr = child?.stderr ?? null;\n let log = \"\";\n stderr?.setEncoding(\"utf8\");\n\n stderr?.on(\"data\", (chunk: string) => {\n log += chunk;\n });\n\n return {\n read: () => log,\n stop: () => {\n stderr?.removeAllListeners(\"data\");\n stderr?.resume();\n\n if (stderr instanceof Socket) {\n stderr.unref();\n }\n },\n };\n};\n\nconst trackChild = (child: ChildProcess, handlers: ChildHandlers): (() => void) => {\n child.on(\"exit\", handlers.exit);\n child.on(\"error\", handlers.error);\n\n return () => {\n child.removeListener(\"exit\", handlers.exit);\n child.removeListener(\"error\", handlers.error);\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 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 stderrSuffix = (child: ChildProcess | undefined, stderr: StderrCapture): string =>\n child ? `\\n${stderr.read()}` : \"\";\n\nconst hasAlreadyAborted = (signal: AbortSignal | undefined, onAbort: () => void, cleanups: (() => void)[]): boolean => {\n if (signal === undefined) {\n return false;\n }\n\n if (signal.aborted) {\n return true;\n }\n\n signal.addEventListener(\"abort\", onAbort);\n\n cleanups.push(() => {\n signal.removeEventListener(\"abort\", onAbort);\n });\n\n return false;\n};\n\nconst watchForSocket = ({ path, options, resolve, reject }: SocketWatch): void => {\n const { label, timeout = 15_000, child, signal } = options;\n const stderr = captureStderr(child);\n const cleanups: (() => void)[] = [stderr.stop];\n\n const stop = (): void => {\n runCleanups(cleanups);\n };\n\n const fail = (message: string): void => {\n stop();\n reject(new Error(message));\n };\n\n const onExit = (code: number | null, terminationSignal: NodeJS.Signals | null): void => {\n fail(`${exitedMessage(label, path, code, terminationSignal)}\\n${stderr.read()}`);\n };\n\n const onError = (cause: Error): void => {\n fail(`${label} failed to spawn: ${cause.message}\\n${stderr.read()}`);\n };\n\n const onAbort = (): void => {\n fail(`${label} startup aborted before ${path} appeared`);\n };\n\n const poll = pollForPath(path, () => {\n stop();\n resolve();\n });\n\n const timer = setTimeout(() => {\n fail(`${label} did not become available within ${String(timeout)}ms${stderrSuffix(child, stderr)}`);\n }, timeout);\n\n cleanups.push(() => {\n clearInterval(poll);\n clearTimeout(timer);\n });\n\n if (child) {\n cleanups.push(trackChild(child, { exit: onExit, error: onError }));\n }\n\n if (hasAlreadyAborted(signal, onAbort, cleanups)) {\n onAbort();\n }\n};\n\nconst waitForSocket = (path: string, options: WaitForSocketOptions): Promise<void> =>\n new Promise((resolve, reject) => {\n watchForSocket({ path, options, resolve, reject });\n });\n\nconst captureCompositorStderr = (child: ChildProcess, logPath: string): string[] => {\n const captured: string[] = [];\n const stderr = child.stderr;\n\n if (stderr !== null) {\n stderr.setEncoding(\"utf8\");\n const logStream = createWriteStream(logPath);\n logStream.on(\"error\", (): void => undefined);\n\n stderr.on(\"data\", (chunk: string) => {\n captured.push(chunk);\n logStream.write(chunk);\n });\n }\n\n return captured;\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 (sockets: DisplaySockets): Promise<void> => {\n const { compositor, compositorSocketPath, busChild, busSocketPath } = sockets;\n const abort = new AbortController();\n\n try {\n await Promise.all([\n waitForSocket(compositorSocketPath, {\n label: \"Compositor\",\n child: compositor.child,\n signal: abort.signal,\n }),\n waitForSocket(busSocketPath, { label: \"D-Bus session bus\", child: busChild, signal: abort.signal }),\n ]);\n } finally {\n abort.abort();\n }\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, { 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(\n \"dbus-daemon\",\n [`--config-file=${busConfigPath}`],\n [\"ignore\", \"ignore\", \"pipe\"],\n );\n\n spawned.push(busChild);\n applyEnv(env, { DBUS_SESSION_BUS_ADDRESS: `unix:path=${busSocketPath}` });\n const compositor = startCompositor(runtimeDir, options, env);\n spawned.push(compositor.child);\n applyEnv(env, { WAYLAND_DISPLAY: compositor.socket });\n const compositorSocketPath = join(runtimeDir, compositor.socket);\n await waitForDisplaySockets({ compositor, compositorSocketPath, busChild, busSocketPath });\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 () => {\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 DEFAULT_HEADLESS_SIZE,\n STATIC_HEADLESS_ENV,\n resolveHeadlessOptions,\n readHeadlessOptions,\n startHeadlessDisplay,\n type CompositorId,\n type HeadlessOptions,\n};\n"]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
declare global {
|
|
2
|
+
var gtkxHeadlessTeardown: (() => void) | undefined;
|
|
3
|
+
var gtkxHeadlessShutdownInstalled: boolean | undefined;
|
|
4
|
+
}
|
|
5
|
+
declare const setHeadlessTeardown: (teardown: (() => void) | undefined) => void;
|
|
6
|
+
declare const headlessTeardown: () => (() => void) | undefined;
|
|
7
|
+
declare const setHeadlessShutdownInstalled: (installed: boolean | undefined) => void;
|
|
8
|
+
declare const isHeadlessShutdownInstalled: () => boolean;
|
|
9
|
+
export { setHeadlessTeardown, headlessTeardown, setHeadlessShutdownInstalled, isHeadlessShutdownInstalled };
|
|
10
|
+
//# sourceMappingURL=headless-globals.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"headless-globals.d.ts","sourceRoot":"","sources":["../src/headless-globals.ts"],"names":[],"mappings":"AAAA,OAAO,CAAC,MAAM,CAAC;IACX,IAAI,oBAAoB,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS,CAAC;IACnD,IAAI,6BAA6B,EAAE,OAAO,GAAG,SAAS,CAAC;CAC1D;AAMD,QAAA,MAAM,mBAAmB,GAAI,UAAU,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS,KAAG,IAEjE,CAAC;AAEF,QAAA,MAAM,gBAAgB,QAAO,CAAC,MAAM,IAAI,CAAC,GAAG,SAA4C,CAAC;AAEzF,QAAA,MAAM,4BAA4B,GAAI,WAAW,OAAO,GAAG,SAAS,KAAG,IAEtE,CAAC;AAEF,QAAA,MAAM,2BAA2B,QAAO,OAA4D,CAAC;AAErG,OAAO,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,4BAA4B,EAAE,2BAA2B,EAAE,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const defineGlobal = (key, value) => {
|
|
2
|
+
Object.defineProperty(globalThis, key, { value, configurable: true, writable: true });
|
|
3
|
+
};
|
|
4
|
+
const setHeadlessTeardown = (teardown) => {
|
|
5
|
+
defineGlobal("gtkxHeadlessTeardown", teardown);
|
|
6
|
+
};
|
|
7
|
+
const headlessTeardown = () => globalThis.gtkxHeadlessTeardown;
|
|
8
|
+
const setHeadlessShutdownInstalled = (installed) => {
|
|
9
|
+
defineGlobal("gtkxHeadlessShutdownInstalled", installed);
|
|
10
|
+
};
|
|
11
|
+
const isHeadlessShutdownInstalled = () => globalThis.gtkxHeadlessShutdownInstalled === true;
|
|
12
|
+
export { setHeadlessTeardown, headlessTeardown, setHeadlessShutdownInstalled, isHeadlessShutdownInstalled };
|
|
13
|
+
//# sourceMappingURL=headless-globals.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"headless-globals.js","sourceRoot":"","sources":["../src/headless-globals.ts"],"names":[],"mappings":"AAKA,MAAM,YAAY,GAAG,CAAC,GAAW,EAAE,KAAc,EAAQ,EAAE;IACvD,MAAM,CAAC,cAAc,CAAC,UAAU,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,YAAY,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;AAC1F,CAAC,CAAC;AAEF,MAAM,mBAAmB,GAAG,CAAC,QAAkC,EAAQ,EAAE;IACrE,YAAY,CAAC,sBAAsB,EAAE,QAAQ,CAAC,CAAC;AACnD,CAAC,CAAC;AAEF,MAAM,gBAAgB,GAAG,GAA6B,EAAE,CAAC,UAAU,CAAC,oBAAoB,CAAC;AAEzF,MAAM,4BAA4B,GAAG,CAAC,SAA8B,EAAQ,EAAE;IAC1E,YAAY,CAAC,+BAA+B,EAAE,SAAS,CAAC,CAAC;AAC7D,CAAC,CAAC;AAEF,MAAM,2BAA2B,GAAG,GAAY,EAAE,CAAC,UAAU,CAAC,6BAA6B,KAAK,IAAI,CAAC;AAErG,OAAO,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,4BAA4B,EAAE,2BAA2B,EAAE,CAAC","sourcesContent":["declare global {\n var gtkxHeadlessTeardown: (() => void) | undefined;\n var gtkxHeadlessShutdownInstalled: boolean | undefined;\n}\n\nconst defineGlobal = (key: string, value: unknown): void => {\n Object.defineProperty(globalThis, key, { value, configurable: true, writable: true });\n};\n\nconst setHeadlessTeardown = (teardown: (() => void) | undefined): void => {\n defineGlobal(\"gtkxHeadlessTeardown\", teardown);\n};\n\nconst headlessTeardown = (): (() => void) | undefined => globalThis.gtkxHeadlessTeardown;\n\nconst setHeadlessShutdownInstalled = (installed: boolean | undefined): void => {\n defineGlobal(\"gtkxHeadlessShutdownInstalled\", installed);\n};\n\nconst isHeadlessShutdownInstalled = (): boolean => globalThis.gtkxHeadlessShutdownInstalled === true;\n\nexport { setHeadlessTeardown, headlessTeardown, setHeadlessShutdownInstalled, isHeadlessShutdownInstalled };\n"]}
|