gestament 0.4.0 → 0.6.0
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/README.md +20 -5
- package/dist/displaySession.d.ts +2 -2
- package/dist/displaySession.d.ts.map +1 -1
- package/dist/element.d.ts +2 -2
- package/dist/element.d.ts.map +1 -1
- package/dist/errors.d.ts +2 -2
- package/dist/generated/packageMetadata.d.ts +4 -4
- package/dist/gestament-config.d.ts +2 -2
- package/dist/gestament-launcher-driver.cjs +103 -5
- package/dist/gestament-launcher-driver.cjs.map +1 -1
- package/dist/gestament-launcher-driver.d.ts +2 -2
- package/dist/gestament-launcher-driver.mjs +103 -5
- package/dist/gestament-launcher-driver.mjs.map +1 -1
- package/dist/gestament-tray-host.cjs +9 -1
- package/dist/gestament-tray-host.cjs.map +1 -1
- package/dist/gestament-tray-host.d.ts +2 -2
- package/dist/gestament-tray-host.mjs +9 -1
- package/dist/gestament-tray-host.mjs.map +1 -1
- package/dist/gestament-xvfb-pool-probe.cjs +1 -1
- package/dist/gestament-xvfb-pool-probe.d.ts +2 -2
- package/dist/gestament-xvfb-pool-probe.mjs +1 -1
- package/dist/gestament-xvfb-worker.d.ts +2 -2
- package/dist/gestament-xvfb.d.ts +2 -2
- package/dist/gestament.cjs +279 -0
- package/dist/gestament.cjs.map +1 -0
- package/dist/gestament.d.ts +29 -0
- package/dist/gestament.d.ts.map +1 -0
- package/dist/gestament.mjs +278 -0
- package/dist/gestament.mjs.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.mjs +1 -1
- package/dist/{launchGtkApp-Bst1BFbD.js → launchGtkApp-B3FMzhrc.js} +741 -74
- package/dist/launchGtkApp-B3FMzhrc.js.map +1 -0
- package/dist/{launchGtkApp-BfELuV-H.cjs → launchGtkApp-OggUtTTC.cjs} +743 -76
- package/dist/launchGtkApp-OggUtTTC.cjs.map +1 -0
- package/dist/launchGtkApp.d.ts +2 -2
- package/dist/launchGtkApp.d.ts.map +1 -1
- package/dist/launcherDriverProtocol.d.ts +37 -4
- package/dist/launcherDriverProtocol.d.ts.map +1 -1
- package/dist/{native-C6MsIBNF.js → native-C2tzSvVW.js} +25 -11
- package/dist/native-C2tzSvVW.js.map +1 -0
- package/dist/{native-BUWDWMBB.cjs → native-Xz0gjKti.cjs} +20 -6
- package/dist/native-Xz0gjKti.cjs.map +1 -0
- package/dist/native.d.ts +8 -2
- package/dist/native.d.ts.map +1 -1
- package/dist/output.d.ts +27 -0
- package/dist/output.d.ts.map +1 -0
- package/dist/packageMetadata-BQE2KNaa.js +5 -0
- package/dist/packageMetadata-BQE2KNaa.js.map +1 -0
- package/dist/packageMetadata-BqAHZ5WO.cjs +4 -0
- package/dist/packageMetadata-BqAHZ5WO.cjs.map +1 -0
- package/dist/prerequisites.d.ts +2 -2
- package/dist/testing.d.ts +2 -2
- package/dist/tray.d.ts +2 -2
- package/dist/types.d.ts +206 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/wait.d.ts +2 -2
- package/package.json +8 -7
- package/prebuilds/linux-arm/gtk3/node.napi.armv7.glibc.node +0 -0
- package/prebuilds/linux-arm/gtk4/node.napi.armv7.glibc.node +0 -0
- package/prebuilds/linux-arm64/gtk3/node.napi.glibc.node +0 -0
- package/prebuilds/linux-arm64/gtk4/node.napi.glibc.node +0 -0
- package/prebuilds/linux-ia32/gtk3/node.napi.glibc.node +0 -0
- package/prebuilds/linux-ia32/gtk4/node.napi.glibc.node +0 -0
- package/prebuilds/linux-riscv64/gtk3/node.napi.glibc.node +0 -0
- package/prebuilds/linux-riscv64/gtk4/node.napi.glibc.node +0 -0
- package/prebuilds/linux-x64/gtk3/node.napi.glibc.node +0 -0
- package/prebuilds/linux-x64/gtk4/node.napi.glibc.node +0 -0
- package/dist/launchGtkApp-BfELuV-H.cjs.map +0 -1
- package/dist/launchGtkApp-Bst1BFbD.js.map +0 -1
- package/dist/native-BUWDWMBB.cjs.map +0 -1
- package/dist/native-C6MsIBNF.js.map +0 -1
|
@@ -8,8 +8,233 @@ const node_os = require("node:os");
|
|
|
8
8
|
const node_path = require("node:path");
|
|
9
9
|
const node_url = require("node:url");
|
|
10
10
|
const prerequisites = require("./prerequisites-BuZST2Dy.cjs");
|
|
11
|
-
const native = require("./native-
|
|
11
|
+
const native = require("./native-Xz0gjKti.cjs");
|
|
12
12
|
var _documentCurrentScript = typeof document !== "undefined" ? document.currentScript : null;
|
|
13
|
+
const normalizeOutputBufferBytes = (value, optionName = "outputBufferBytes") => {
|
|
14
|
+
if (value === void 0) {
|
|
15
|
+
return void 0;
|
|
16
|
+
}
|
|
17
|
+
if (!Number.isSafeInteger(value) || value < 0) {
|
|
18
|
+
throw errors.createGtkInvalidArgumentError(
|
|
19
|
+
`${optionName} must be a non-negative safe integer.`
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
return value;
|
|
23
|
+
};
|
|
24
|
+
const createStreamState = () => ({
|
|
25
|
+
byteLength: 0,
|
|
26
|
+
chunks: [],
|
|
27
|
+
decoder: new TextDecoder(),
|
|
28
|
+
flushed: false,
|
|
29
|
+
truncated: false
|
|
30
|
+
});
|
|
31
|
+
const appendBoundedChunk = (state, chunk, maxBytes) => {
|
|
32
|
+
if (chunk.length === 0) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (maxBytes === void 0) {
|
|
36
|
+
state.chunks.push(Buffer.from(chunk));
|
|
37
|
+
state.byteLength += chunk.length;
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (maxBytes === 0) {
|
|
41
|
+
state.truncated = true;
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (chunk.length >= maxBytes) {
|
|
45
|
+
state.chunks.splice(
|
|
46
|
+
0,
|
|
47
|
+
state.chunks.length,
|
|
48
|
+
Buffer.from(chunk.subarray(chunk.length - maxBytes))
|
|
49
|
+
);
|
|
50
|
+
state.byteLength = maxBytes;
|
|
51
|
+
state.truncated = true;
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
state.chunks.push(Buffer.from(chunk));
|
|
55
|
+
state.byteLength += chunk.length;
|
|
56
|
+
while (state.byteLength > maxBytes) {
|
|
57
|
+
const first = state.chunks[0];
|
|
58
|
+
if (first === void 0) {
|
|
59
|
+
state.byteLength = 0;
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const excess = state.byteLength - maxBytes;
|
|
63
|
+
if (first.length <= excess) {
|
|
64
|
+
state.chunks.shift();
|
|
65
|
+
state.byteLength -= first.length;
|
|
66
|
+
state.truncated = true;
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
state.chunks[0] = Buffer.from(first.subarray(excess));
|
|
70
|
+
state.byteLength -= excess;
|
|
71
|
+
state.truncated = true;
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
const streamText = (state) => Buffer.concat(state.chunks, state.byteLength).toString("utf8");
|
|
75
|
+
const createOutputEvent = (nextSequence, create, text) => {
|
|
76
|
+
if (text.length === 0) {
|
|
77
|
+
return void 0;
|
|
78
|
+
}
|
|
79
|
+
return create(nextSequence());
|
|
80
|
+
};
|
|
81
|
+
const createGtkAppOutputRecorder = (outputBufferBytes) => {
|
|
82
|
+
const maxBytes = normalizeOutputBufferBytes(outputBufferBytes);
|
|
83
|
+
const stdout = createStreamState();
|
|
84
|
+
const stderr = createStreamState();
|
|
85
|
+
let nextSequence = 0;
|
|
86
|
+
const streamState = (stream) => stream === "stdout" ? stdout : stderr;
|
|
87
|
+
const takeSequence = () => {
|
|
88
|
+
const sequence = nextSequence;
|
|
89
|
+
nextSequence += 1;
|
|
90
|
+
return sequence;
|
|
91
|
+
};
|
|
92
|
+
const createEvent = (stream, text) => createOutputEvent(
|
|
93
|
+
takeSequence,
|
|
94
|
+
(sequence) => ({
|
|
95
|
+
sequence,
|
|
96
|
+
stream,
|
|
97
|
+
text,
|
|
98
|
+
timestampMs: Date.now()
|
|
99
|
+
}),
|
|
100
|
+
text
|
|
101
|
+
);
|
|
102
|
+
return {
|
|
103
|
+
append: (stream, chunk) => {
|
|
104
|
+
const state = streamState(stream);
|
|
105
|
+
appendBoundedChunk(state, chunk, maxBytes);
|
|
106
|
+
return createEvent(stream, state.decoder.decode(chunk, { stream: true }));
|
|
107
|
+
},
|
|
108
|
+
flush: (stream) => {
|
|
109
|
+
const state = streamState(stream);
|
|
110
|
+
if (state.flushed) {
|
|
111
|
+
return void 0;
|
|
112
|
+
}
|
|
113
|
+
state.flushed = true;
|
|
114
|
+
return createEvent(stream, state.decoder.decode());
|
|
115
|
+
},
|
|
116
|
+
snapshot: (exitCode, exitSignal) => ({
|
|
117
|
+
exitCode,
|
|
118
|
+
exitSignal,
|
|
119
|
+
stderr: streamText(stderr),
|
|
120
|
+
stderrTruncated: stderr.truncated,
|
|
121
|
+
stdout: streamText(stdout),
|
|
122
|
+
stdoutTruncated: stdout.truncated
|
|
123
|
+
})
|
|
124
|
+
};
|
|
125
|
+
};
|
|
126
|
+
const systemOutputSources = [
|
|
127
|
+
"xvfb",
|
|
128
|
+
"launcher-driver",
|
|
129
|
+
"tray-host"
|
|
130
|
+
];
|
|
131
|
+
const createSourceState = () => ({
|
|
132
|
+
stderr: createStreamState(),
|
|
133
|
+
stdout: createStreamState()
|
|
134
|
+
});
|
|
135
|
+
const createGtkSystemOutputRecorder = (systemOutputBufferBytes) => {
|
|
136
|
+
const maxBytes = normalizeOutputBufferBytes(
|
|
137
|
+
systemOutputBufferBytes,
|
|
138
|
+
"systemOutputBufferBytes"
|
|
139
|
+
);
|
|
140
|
+
const sources = /* @__PURE__ */ new Map();
|
|
141
|
+
let nextSequence = 0;
|
|
142
|
+
const takeSequence = () => {
|
|
143
|
+
const sequence = nextSequence;
|
|
144
|
+
nextSequence += 1;
|
|
145
|
+
return sequence;
|
|
146
|
+
};
|
|
147
|
+
const sourceState = (source) => {
|
|
148
|
+
const existing = sources.get(source);
|
|
149
|
+
if (existing !== void 0) {
|
|
150
|
+
return existing;
|
|
151
|
+
}
|
|
152
|
+
const state = createSourceState();
|
|
153
|
+
sources.set(source, state);
|
|
154
|
+
return state;
|
|
155
|
+
};
|
|
156
|
+
const streamState = (source, stream) => {
|
|
157
|
+
const state = sourceState(source);
|
|
158
|
+
return stream === "stdout" ? state.stdout : state.stderr;
|
|
159
|
+
};
|
|
160
|
+
const existingStreamState = (source, stream) => {
|
|
161
|
+
const state = sources.get(source);
|
|
162
|
+
if (state === void 0) {
|
|
163
|
+
return void 0;
|
|
164
|
+
}
|
|
165
|
+
return stream === "stdout" ? state.stdout : state.stderr;
|
|
166
|
+
};
|
|
167
|
+
const createEvent = (source, stream, text) => createOutputEvent(
|
|
168
|
+
takeSequence,
|
|
169
|
+
(sequence) => ({
|
|
170
|
+
sequence,
|
|
171
|
+
source,
|
|
172
|
+
stream,
|
|
173
|
+
text,
|
|
174
|
+
timestampMs: Date.now()
|
|
175
|
+
}),
|
|
176
|
+
text
|
|
177
|
+
);
|
|
178
|
+
const sourceSnapshot = (source, state) => ({
|
|
179
|
+
source,
|
|
180
|
+
stderr: streamText(state.stderr),
|
|
181
|
+
stderrTruncated: state.stderr.truncated,
|
|
182
|
+
stdout: streamText(state.stdout),
|
|
183
|
+
stdoutTruncated: state.stdout.truncated
|
|
184
|
+
});
|
|
185
|
+
return {
|
|
186
|
+
append: (source, stream, chunk) => {
|
|
187
|
+
const state = streamState(source, stream);
|
|
188
|
+
appendBoundedChunk(state, chunk, maxBytes);
|
|
189
|
+
return createEvent(
|
|
190
|
+
source,
|
|
191
|
+
stream,
|
|
192
|
+
state.decoder.decode(chunk, { stream: true })
|
|
193
|
+
);
|
|
194
|
+
},
|
|
195
|
+
flush: (source, stream) => {
|
|
196
|
+
const state = existingStreamState(source, stream);
|
|
197
|
+
if (state === void 0) {
|
|
198
|
+
return void 0;
|
|
199
|
+
}
|
|
200
|
+
if (state.flushed) {
|
|
201
|
+
return void 0;
|
|
202
|
+
}
|
|
203
|
+
state.flushed = true;
|
|
204
|
+
return createEvent(source, stream, state.decoder.decode());
|
|
205
|
+
},
|
|
206
|
+
snapshot: () => ({
|
|
207
|
+
sources: systemOutputSources.flatMap((source) => {
|
|
208
|
+
const state = sources.get(source);
|
|
209
|
+
return state === void 0 ? [] : [sourceSnapshot(source, state)];
|
|
210
|
+
})
|
|
211
|
+
})
|
|
212
|
+
};
|
|
213
|
+
};
|
|
214
|
+
const notifyGtkAppOutput = (callback, event) => {
|
|
215
|
+
if (callback === void 0 || event === void 0) {
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
try {
|
|
219
|
+
callback(event);
|
|
220
|
+
} catch (error) {
|
|
221
|
+
queueMicrotask(() => {
|
|
222
|
+
throw error;
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
const notifyGtkSystemOutput = (callback, event) => {
|
|
227
|
+
if (callback === void 0 || event === void 0) {
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
try {
|
|
231
|
+
callback(event);
|
|
232
|
+
} catch (error) {
|
|
233
|
+
queueMicrotask(() => {
|
|
234
|
+
throw error;
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
};
|
|
13
238
|
const defaultDisplay = "xvfb";
|
|
14
239
|
const defaultGSettings = "memory";
|
|
15
240
|
const defaultTheme = "Adwaita";
|
|
@@ -22,6 +247,9 @@ const sessionStartupTimeoutMs = 3e4;
|
|
|
22
247
|
const sessionReleaseTimeoutMs = 5e3;
|
|
23
248
|
const xvfbStartupTimeoutMs = 1e4;
|
|
24
249
|
const xvfbPoolProbeTimeoutMs = 5e3;
|
|
250
|
+
const xvfbPoolProbeRetryIntervalMs = 50;
|
|
251
|
+
const xvfbPoolProbePrefix = "gestament-xvfb-pool-probe: ";
|
|
252
|
+
const x11DisplayOpenFailureMessage = "Failed to open the X11 display. Ensure DISPLAY points to an X11 display.";
|
|
25
253
|
const firstPooledDisplayNumber = 90;
|
|
26
254
|
const lastPooledDisplayNumber = 590;
|
|
27
255
|
const sessionOwnedEnvironmentKeys = [
|
|
@@ -35,6 +263,7 @@ const sessionOwnedEnvironmentKeys = [
|
|
|
35
263
|
"GESTAMENT_XVFB_ACTIVE",
|
|
36
264
|
"XDG_SESSION_TYPE"
|
|
37
265
|
];
|
|
266
|
+
let outputScopeCounter = 0;
|
|
38
267
|
let socketCounter = 0;
|
|
39
268
|
const leasedDisplayNumbers = /* @__PURE__ */ new Set();
|
|
40
269
|
const idleXvfbByKey = /* @__PURE__ */ new Map();
|
|
@@ -51,6 +280,12 @@ const appendOutput$1 = (lines, chunk) => {
|
|
|
51
280
|
lines.splice(0, lines.length - 40);
|
|
52
281
|
}
|
|
53
282
|
};
|
|
283
|
+
const appendSystemOutput = (sink, source, stream, chunk) => {
|
|
284
|
+
sink?.append(source, stream, chunk);
|
|
285
|
+
};
|
|
286
|
+
const flushSystemOutput = (sink, source, stream) => {
|
|
287
|
+
sink?.flush(source, stream);
|
|
288
|
+
};
|
|
54
289
|
const unrefHandle = (handle) => {
|
|
55
290
|
const maybeRefHandle = handle;
|
|
56
291
|
if (typeof maybeRefHandle.unref === "function") {
|
|
@@ -69,6 +304,41 @@ ${stdoutText}
|
|
|
69
304
|
stderr:
|
|
70
305
|
${stderrText}`;
|
|
71
306
|
};
|
|
307
|
+
const createXvfbProbeError = (message, retryable) => {
|
|
308
|
+
const error = errors.createGtkOperationFailedError(message);
|
|
309
|
+
Object.defineProperty(error, "retryable", {
|
|
310
|
+
value: retryable
|
|
311
|
+
});
|
|
312
|
+
return error;
|
|
313
|
+
};
|
|
314
|
+
const parseXvfbProbeErrorPayload = (stderrText) => {
|
|
315
|
+
const lines = stderrText.trim().split("\n").reverse();
|
|
316
|
+
for (const line of lines) {
|
|
317
|
+
if (!line.startsWith(xvfbPoolProbePrefix)) {
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
try {
|
|
321
|
+
const value = JSON.parse(
|
|
322
|
+
line.slice(xvfbPoolProbePrefix.length)
|
|
323
|
+
);
|
|
324
|
+
if (!isRecord(value)) {
|
|
325
|
+
return void 0;
|
|
326
|
+
}
|
|
327
|
+
return {
|
|
328
|
+
code: typeof value.code === "string" ? value.code : void 0,
|
|
329
|
+
message: typeof value.message === "string" ? value.message : void 0
|
|
330
|
+
};
|
|
331
|
+
} catch {
|
|
332
|
+
return void 0;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
return void 0;
|
|
336
|
+
};
|
|
337
|
+
const isRetryableXvfbProbeExit = (stderrText) => {
|
|
338
|
+
const payload = parseXvfbProbeErrorPayload(stderrText);
|
|
339
|
+
return payload?.code === "OPERATION_FAILED" && payload.message === x11DisplayOpenFailureMessage;
|
|
340
|
+
};
|
|
341
|
+
const isRetryableXvfbProbeError = (error) => isRecord(error) && typeof error.retryable === "boolean" && error.retryable;
|
|
72
342
|
const getHostDisplayState = () => ({
|
|
73
343
|
display: process.env.DISPLAY,
|
|
74
344
|
waylandDisplay: process.env.WAYLAND_DISPLAY
|
|
@@ -94,6 +364,63 @@ const resolveDisplay = (display) => {
|
|
|
94
364
|
);
|
|
95
365
|
};
|
|
96
366
|
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
367
|
+
const createDriverEventKey = (channel, scopeId) => `${channel}
|
|
368
|
+
${scopeId}`;
|
|
369
|
+
const createOutputScopeId = () => {
|
|
370
|
+
const scopeId = `output-${process.pid}-${outputScopeCounter}`;
|
|
371
|
+
outputScopeCounter += 1;
|
|
372
|
+
return scopeId;
|
|
373
|
+
};
|
|
374
|
+
const isDriverEventMessage = (message) => isRecord(message) && message.type === "event" && typeof message.channel === "string" && typeof message.scopeId === "string";
|
|
375
|
+
const resolveOutputBufferBytes = (launcherValue, launchValue) => {
|
|
376
|
+
const value = launchValue ?? launcherValue;
|
|
377
|
+
if (value === void 0) {
|
|
378
|
+
return null;
|
|
379
|
+
}
|
|
380
|
+
if (!Number.isSafeInteger(value) || value < 0) {
|
|
381
|
+
throw errors.createGtkInvalidArgumentError(
|
|
382
|
+
"outputBufferBytes must be a non-negative safe integer."
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
return value;
|
|
386
|
+
};
|
|
387
|
+
const createSystemOutputSink = (recorder, callback) => ({
|
|
388
|
+
append: (source, stream, chunk) => {
|
|
389
|
+
notifyGtkSystemOutput(callback, recorder.append(source, stream, chunk));
|
|
390
|
+
},
|
|
391
|
+
flush: (source, stream) => {
|
|
392
|
+
notifyGtkSystemOutput(callback, recorder.flush(source, stream));
|
|
393
|
+
}
|
|
394
|
+
});
|
|
395
|
+
const isGtkSystemOutputSource = (value) => value === "xvfb" || value === "launcher-driver" || value === "tray-host";
|
|
396
|
+
const isGtkAppOutputStream = (value) => value === "stdout" || value === "stderr";
|
|
397
|
+
const isWireGtkSystemOutput = (value) => {
|
|
398
|
+
if (!isRecord(value)) {
|
|
399
|
+
return false;
|
|
400
|
+
}
|
|
401
|
+
if (!isGtkSystemOutputSource(value.source) || !isGtkAppOutputStream(value.stream)) {
|
|
402
|
+
return false;
|
|
403
|
+
}
|
|
404
|
+
if (value.type === "flush") {
|
|
405
|
+
return true;
|
|
406
|
+
}
|
|
407
|
+
return value.type === "chunk" && typeof value.chunkBase64 === "string";
|
|
408
|
+
};
|
|
409
|
+
const routeWireSystemOutput = (sink, value) => {
|
|
410
|
+
if (!isWireGtkSystemOutput(value)) {
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
if (value.type === "flush") {
|
|
414
|
+
flushSystemOutput(sink, value.source, value.stream);
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
appendSystemOutput(
|
|
418
|
+
sink,
|
|
419
|
+
value.source,
|
|
420
|
+
value.stream,
|
|
421
|
+
Buffer.from(value.chunkBase64, "base64")
|
|
422
|
+
);
|
|
423
|
+
};
|
|
97
424
|
const resolvePoolLimit = (name, value, defaultValue) => {
|
|
98
425
|
if (value === void 0) {
|
|
99
426
|
return defaultValue;
|
|
@@ -241,6 +568,7 @@ const retainIdleXvfb = async (xvfb, limits) => {
|
|
|
241
568
|
await terminateXvfb(xvfb);
|
|
242
569
|
return;
|
|
243
570
|
}
|
|
571
|
+
xvfb.systemOutputSink = void 0;
|
|
244
572
|
xvfb.lastUsedAt = Date.now();
|
|
245
573
|
pushArrayEntry(idleXvfbByKey, xvfb.key, xvfb);
|
|
246
574
|
await trimIdleXvfbKey(xvfb.key, limits.maxIdlePerKey);
|
|
@@ -251,6 +579,8 @@ const retainIdleAllSession = async (session, limits) => {
|
|
|
251
579
|
await session.session.terminate();
|
|
252
580
|
return;
|
|
253
581
|
}
|
|
582
|
+
session.session.setSystemOutputSink(void 0);
|
|
583
|
+
session.xvfb.systemOutputSink = void 0;
|
|
254
584
|
session.lastUsedAt = Date.now();
|
|
255
585
|
session.xvfb.lastUsedAt = session.lastUsedAt;
|
|
256
586
|
pushArrayEntry(idleAllByKey, session.key, session);
|
|
@@ -354,7 +684,7 @@ const resolveLauncherEnvironment = (options, effective) => {
|
|
|
354
684
|
};
|
|
355
685
|
const resolveDriverPath = () => {
|
|
356
686
|
const driverPath = node_path.resolve(
|
|
357
|
-
node_path.dirname(node_url.fileURLToPath(typeof document === "undefined" ? require("url").pathToFileURL(__filename).href : _documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === "SCRIPT" && _documentCurrentScript.src || new URL("launchGtkApp-
|
|
687
|
+
node_path.dirname(node_url.fileURLToPath(typeof document === "undefined" ? require("url").pathToFileURL(__filename).href : _documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === "SCRIPT" && _documentCurrentScript.src || new URL("launchGtkApp-OggUtTTC.cjs", document.baseURI).href)),
|
|
358
688
|
"..",
|
|
359
689
|
"dist",
|
|
360
690
|
"gestament-launcher-driver.cjs"
|
|
@@ -368,7 +698,7 @@ const resolveDriverPath = () => {
|
|
|
368
698
|
};
|
|
369
699
|
const resolveXvfbPoolProbePath = () => {
|
|
370
700
|
const probePath = node_path.resolve(
|
|
371
|
-
node_path.dirname(node_url.fileURLToPath(typeof document === "undefined" ? require("url").pathToFileURL(__filename).href : _documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === "SCRIPT" && _documentCurrentScript.src || new URL("launchGtkApp-
|
|
701
|
+
node_path.dirname(node_url.fileURLToPath(typeof document === "undefined" ? require("url").pathToFileURL(__filename).href : _documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === "SCRIPT" && _documentCurrentScript.src || new URL("launchGtkApp-OggUtTTC.cjs", document.baseURI).href)),
|
|
372
702
|
"..",
|
|
373
703
|
"dist",
|
|
374
704
|
"gestament-xvfb-pool-probe.cjs"
|
|
@@ -463,7 +793,7 @@ const installPoolCleanup = () => {
|
|
|
463
793
|
}
|
|
464
794
|
});
|
|
465
795
|
};
|
|
466
|
-
const spawnDirectXvfb = async (screen) => {
|
|
796
|
+
const spawnDirectXvfb = async (screen, systemOutputSink) => {
|
|
467
797
|
installPoolCleanup();
|
|
468
798
|
for (let displayNumber = firstPooledDisplayNumber; displayNumber <= lastPooledDisplayNumber; displayNumber += 1) {
|
|
469
799
|
if (!isDisplayNumberAvailable(displayNumber)) {
|
|
@@ -480,11 +810,30 @@ const spawnDirectXvfb = async (screen) => {
|
|
|
480
810
|
stdio: ["ignore", "pipe", "pipe"]
|
|
481
811
|
}
|
|
482
812
|
);
|
|
813
|
+
const xvfb = {
|
|
814
|
+
child,
|
|
815
|
+
display: `:${displayNumber}`,
|
|
816
|
+
displayNumber,
|
|
817
|
+
key: screen,
|
|
818
|
+
lastUsedAt: Date.now(),
|
|
819
|
+
screen,
|
|
820
|
+
stderr,
|
|
821
|
+
stdout,
|
|
822
|
+
systemOutputSink
|
|
823
|
+
};
|
|
483
824
|
child.stdout.on("data", (chunk) => {
|
|
484
825
|
appendOutput$1(stdout, chunk);
|
|
826
|
+
appendSystemOutput(xvfb.systemOutputSink, "xvfb", "stdout", chunk);
|
|
485
827
|
});
|
|
486
828
|
child.stderr.on("data", (chunk) => {
|
|
487
829
|
appendOutput$1(stderr, chunk);
|
|
830
|
+
appendSystemOutput(xvfb.systemOutputSink, "xvfb", "stderr", chunk);
|
|
831
|
+
});
|
|
832
|
+
child.stdout.once("end", () => {
|
|
833
|
+
flushSystemOutput(xvfb.systemOutputSink, "xvfb", "stdout");
|
|
834
|
+
});
|
|
835
|
+
child.stderr.once("end", () => {
|
|
836
|
+
flushSystemOutput(xvfb.systemOutputSink, "xvfb", "stderr");
|
|
488
837
|
});
|
|
489
838
|
child.unref();
|
|
490
839
|
unrefHandle(child.stdout);
|
|
@@ -507,22 +856,10 @@ const spawnDirectXvfb = async (screen) => {
|
|
|
507
856
|
});
|
|
508
857
|
})
|
|
509
858
|
]);
|
|
510
|
-
const xvfb = {
|
|
511
|
-
child,
|
|
512
|
-
display: `:${displayNumber}`,
|
|
513
|
-
displayNumber,
|
|
514
|
-
key: screen,
|
|
515
|
-
lastUsedAt: Date.now(),
|
|
516
|
-
screen,
|
|
517
|
-
stderr,
|
|
518
|
-
stdout
|
|
519
|
-
};
|
|
520
859
|
directXvfbs.add(xvfb);
|
|
521
860
|
return xvfb;
|
|
522
861
|
} catch (error) {
|
|
523
|
-
killXvfbNow(
|
|
524
|
-
child
|
|
525
|
-
});
|
|
862
|
+
killXvfbNow(xvfb);
|
|
526
863
|
leasedDisplayNumbers.delete(displayNumber);
|
|
527
864
|
const message = error instanceof Error ? error.message : String(error);
|
|
528
865
|
if (message.includes("ENOENT")) {
|
|
@@ -550,22 +887,24 @@ const terminateXvfb = async (xvfb) => {
|
|
|
550
887
|
await delay(25);
|
|
551
888
|
}
|
|
552
889
|
}
|
|
890
|
+
xvfb.systemOutputSink = void 0;
|
|
553
891
|
leasedDisplayNumbers.delete(xvfb.displayNumber);
|
|
554
892
|
};
|
|
555
|
-
const leaseXvfb = async (screen) => {
|
|
893
|
+
const leaseXvfb = async (screen, systemOutputSink) => {
|
|
556
894
|
for (; ; ) {
|
|
557
895
|
const idle = popArrayEntry(idleXvfbByKey, screen);
|
|
558
896
|
if (idle === void 0) {
|
|
559
|
-
return spawnDirectXvfb(screen);
|
|
897
|
+
return spawnDirectXvfb(screen, systemOutputSink);
|
|
560
898
|
}
|
|
561
899
|
if (idle.child.exitCode === null && idle.child.signalCode === null) {
|
|
562
900
|
idle.lastUsedAt = Date.now();
|
|
901
|
+
idle.systemOutputSink = systemOutputSink;
|
|
563
902
|
return idle;
|
|
564
903
|
}
|
|
565
904
|
await terminateXvfb(idle);
|
|
566
905
|
}
|
|
567
906
|
};
|
|
568
|
-
const
|
|
907
|
+
const runXvfbProbeOnce = (xvfb, timeoutMs) => new Promise((resolveProbe, rejectProbe) => {
|
|
569
908
|
const probePath = resolveXvfbPoolProbePath();
|
|
570
909
|
const stdout = [];
|
|
571
910
|
const stderr = [];
|
|
@@ -585,12 +924,32 @@ const runXvfbProbe = (xvfb) => new Promise((resolveProbe, rejectProbe) => {
|
|
|
585
924
|
env,
|
|
586
925
|
stdio: ["ignore", "pipe", "pipe"]
|
|
587
926
|
});
|
|
588
|
-
|
|
927
|
+
let settled = false;
|
|
928
|
+
let timeout;
|
|
929
|
+
const rejectOnce = (error) => {
|
|
930
|
+
if (settled) {
|
|
931
|
+
return;
|
|
932
|
+
}
|
|
933
|
+
settled = true;
|
|
934
|
+
if (timeout !== void 0) {
|
|
935
|
+
clearTimeout(timeout);
|
|
936
|
+
}
|
|
937
|
+
rejectProbe(error);
|
|
938
|
+
};
|
|
939
|
+
const resolveOnce = (result) => {
|
|
940
|
+
if (settled) {
|
|
941
|
+
return;
|
|
942
|
+
}
|
|
943
|
+
settled = true;
|
|
944
|
+
if (timeout !== void 0) {
|
|
945
|
+
clearTimeout(timeout);
|
|
946
|
+
}
|
|
947
|
+
resolveProbe(result);
|
|
948
|
+
};
|
|
949
|
+
timeout = setTimeout(() => {
|
|
589
950
|
child.kill("SIGKILL");
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
);
|
|
593
|
-
}, xvfbPoolProbeTimeoutMs);
|
|
951
|
+
rejectOnce(createXvfbProbeError("Timed out probing Xvfb pool.", false));
|
|
952
|
+
}, timeoutMs);
|
|
594
953
|
child.stdout.on("data", (chunk) => {
|
|
595
954
|
appendOutput$1(stdout, chunk);
|
|
596
955
|
});
|
|
@@ -598,42 +957,71 @@ const runXvfbProbe = (xvfb) => new Promise((resolveProbe, rejectProbe) => {
|
|
|
598
957
|
appendOutput$1(stderr, chunk);
|
|
599
958
|
});
|
|
600
959
|
child.once("error", (error) => {
|
|
601
|
-
|
|
602
|
-
rejectProbe(error);
|
|
960
|
+
rejectOnce(error);
|
|
603
961
|
});
|
|
604
962
|
child.once("exit", (code, signal) => {
|
|
605
|
-
clearTimeout(timeout);
|
|
606
963
|
if (code !== 0) {
|
|
607
|
-
|
|
608
|
-
|
|
964
|
+
const stderrText = stderr.join("");
|
|
965
|
+
rejectOnce(
|
|
966
|
+
createXvfbProbeError(
|
|
609
967
|
`Xvfb pool probe failed: code=${String(code)}, signal=${String(
|
|
610
968
|
signal
|
|
611
|
-
)}` + formatOutputTail(stdout, stderr)
|
|
969
|
+
)}` + formatOutputTail(stdout, stderr),
|
|
970
|
+
isRetryableXvfbProbeExit(stderrText)
|
|
612
971
|
)
|
|
613
972
|
);
|
|
614
973
|
return;
|
|
615
974
|
}
|
|
616
975
|
const output = stdout.join("").trim().split("\n").at(-1);
|
|
617
976
|
if (output === void 0 || output.length === 0) {
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
"Xvfb pool probe did not return a result." + formatOutputTail(stdout, stderr)
|
|
977
|
+
rejectOnce(
|
|
978
|
+
createXvfbProbeError(
|
|
979
|
+
"Xvfb pool probe did not return a result." + formatOutputTail(stdout, stderr),
|
|
980
|
+
false
|
|
621
981
|
)
|
|
622
982
|
);
|
|
623
983
|
return;
|
|
624
984
|
}
|
|
625
985
|
try {
|
|
626
|
-
|
|
986
|
+
resolveOnce(JSON.parse(output));
|
|
627
987
|
} catch (error) {
|
|
628
988
|
const message = error instanceof Error ? error.message : String(error);
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
`Xvfb pool probe returned invalid JSON: ${message}` + formatOutputTail(stdout, stderr)
|
|
989
|
+
rejectOnce(
|
|
990
|
+
createXvfbProbeError(
|
|
991
|
+
`Xvfb pool probe returned invalid JSON: ${message}` + formatOutputTail(stdout, stderr),
|
|
992
|
+
false
|
|
632
993
|
)
|
|
633
994
|
);
|
|
634
995
|
}
|
|
635
996
|
});
|
|
636
997
|
});
|
|
998
|
+
const runXvfbProbe = async (xvfb) => {
|
|
999
|
+
const startedAt = Date.now();
|
|
1000
|
+
let lastError;
|
|
1001
|
+
while (Date.now() - startedAt < xvfbPoolProbeTimeoutMs) {
|
|
1002
|
+
const remainingTimeoutMs = Math.max(
|
|
1003
|
+
1,
|
|
1004
|
+
xvfbPoolProbeTimeoutMs - (Date.now() - startedAt)
|
|
1005
|
+
);
|
|
1006
|
+
try {
|
|
1007
|
+
return await runXvfbProbeOnce(xvfb, remainingTimeoutMs);
|
|
1008
|
+
} catch (error) {
|
|
1009
|
+
lastError = error;
|
|
1010
|
+
if (!isRetryableXvfbProbeError(error)) {
|
|
1011
|
+
throw error;
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
const remainingDelayMs = xvfbPoolProbeTimeoutMs - (Date.now() - startedAt);
|
|
1015
|
+
if (remainingDelayMs <= 0) {
|
|
1016
|
+
break;
|
|
1017
|
+
}
|
|
1018
|
+
await delay(Math.min(xvfbPoolProbeRetryIntervalMs, remainingDelayMs));
|
|
1019
|
+
}
|
|
1020
|
+
if (lastError instanceof Error) {
|
|
1021
|
+
throw lastError;
|
|
1022
|
+
}
|
|
1023
|
+
throw errors.createGtkOperationFailedError("Timed out probing Xvfb pool.");
|
|
1024
|
+
};
|
|
637
1025
|
const cleanCheckXvfb = async (xvfb, allowedMappedWindowCount) => {
|
|
638
1026
|
try {
|
|
639
1027
|
const probe = await runXvfbProbe(xvfb);
|
|
@@ -652,7 +1040,7 @@ const returnXvfbToPool = async (xvfb, limits) => {
|
|
|
652
1040
|
};
|
|
653
1041
|
const allPoolKey = (xvfb) => `${xvfb.screen}
|
|
654
1042
|
${xvfb.trayHost ? "tray" : "no-tray"}`;
|
|
655
|
-
const spawnDriverProcess = (driverPath, socketPath, effective, xvfb) => {
|
|
1043
|
+
const spawnDriverProcess = (driverPath, socketPath, effective, xvfb, systemOutputSink) => {
|
|
656
1044
|
const driverArgs = [
|
|
657
1045
|
"--socket",
|
|
658
1046
|
socketPath,
|
|
@@ -685,14 +1073,46 @@ const spawnDriverProcess = (driverPath, socketPath, effective, xvfb) => {
|
|
|
685
1073
|
env,
|
|
686
1074
|
stdio: ["ignore", "pipe", "pipe"]
|
|
687
1075
|
});
|
|
1076
|
+
const processState = {
|
|
1077
|
+
child,
|
|
1078
|
+
commandLine: [command.bin, ...command.args].join(" "),
|
|
1079
|
+
stderr,
|
|
1080
|
+
stdout,
|
|
1081
|
+
systemOutputSink
|
|
1082
|
+
};
|
|
688
1083
|
child.stdout.on("data", (chunk) => {
|
|
689
1084
|
appendOutput$1(stdout, chunk);
|
|
1085
|
+
appendSystemOutput(
|
|
1086
|
+
processState.systemOutputSink,
|
|
1087
|
+
"launcher-driver",
|
|
1088
|
+
"stdout",
|
|
1089
|
+
chunk
|
|
1090
|
+
);
|
|
690
1091
|
});
|
|
691
1092
|
child.stderr.on("data", (chunk) => {
|
|
692
1093
|
appendOutput$1(stderr, chunk);
|
|
1094
|
+
appendSystemOutput(
|
|
1095
|
+
processState.systemOutputSink,
|
|
1096
|
+
"launcher-driver",
|
|
1097
|
+
"stderr",
|
|
1098
|
+
chunk
|
|
1099
|
+
);
|
|
1100
|
+
});
|
|
1101
|
+
child.stdout.once("end", () => {
|
|
1102
|
+
flushSystemOutput(
|
|
1103
|
+
processState.systemOutputSink,
|
|
1104
|
+
"launcher-driver",
|
|
1105
|
+
"stdout"
|
|
1106
|
+
);
|
|
693
1107
|
});
|
|
694
|
-
|
|
695
|
-
|
|
1108
|
+
child.stderr.once("end", () => {
|
|
1109
|
+
flushSystemOutput(
|
|
1110
|
+
processState.systemOutputSink,
|
|
1111
|
+
"launcher-driver",
|
|
1112
|
+
"stderr"
|
|
1113
|
+
);
|
|
1114
|
+
});
|
|
1115
|
+
return processState;
|
|
696
1116
|
};
|
|
697
1117
|
const listenOnSocket = (server, socketPath) => new Promise((resolveListen, rejectListen) => {
|
|
698
1118
|
const rejectFromError = (error) => {
|
|
@@ -771,7 +1191,13 @@ const waitForDriverReady = (server, processState) => new Promise((resolveReady,
|
|
|
771
1191
|
const line = input.slice(0, newlineIndex);
|
|
772
1192
|
input = input.slice(newlineIndex + 1);
|
|
773
1193
|
try {
|
|
774
|
-
|
|
1194
|
+
const message = JSON.parse(line);
|
|
1195
|
+
if (isDriverEventMessage(message)) {
|
|
1196
|
+
const event = message;
|
|
1197
|
+
if (event.channel === "system.output") {
|
|
1198
|
+
routeWireSystemOutput(processState.systemOutputSink, event.value);
|
|
1199
|
+
}
|
|
1200
|
+
} else if (isRecord(message) && message.type === "ready" && parseReadyMessage(line) !== void 0) {
|
|
775
1201
|
if (settled) {
|
|
776
1202
|
return;
|
|
777
1203
|
}
|
|
@@ -849,6 +1275,7 @@ const decodeCapture = (capture) => ({
|
|
|
849
1275
|
visibleBounds: capture.visibleBounds
|
|
850
1276
|
});
|
|
851
1277
|
const createDriverSession = (socket, bufferedInput, processState, tempDirectory, poolOptions) => {
|
|
1278
|
+
const eventHandlers = /* @__PURE__ */ new Map();
|
|
852
1279
|
const pending = /* @__PURE__ */ new Map();
|
|
853
1280
|
let input = bufferedInput;
|
|
854
1281
|
let nextRequestId = 1;
|
|
@@ -868,8 +1295,34 @@ const createDriverSession = (socket, bufferedInput, processState, tempDirectory,
|
|
|
868
1295
|
closed = true;
|
|
869
1296
|
rejectPending(errors.createGtkAppExitedError("Launcher driver has exited."));
|
|
870
1297
|
};
|
|
1298
|
+
const handleEvent = (event) => {
|
|
1299
|
+
if (event.channel === "system.output") {
|
|
1300
|
+
routeWireSystemOutput(processState.systemOutputSink, event.value);
|
|
1301
|
+
return;
|
|
1302
|
+
}
|
|
1303
|
+
const handlers = eventHandlers.get(
|
|
1304
|
+
createDriverEventKey(event.channel, event.scopeId)
|
|
1305
|
+
);
|
|
1306
|
+
if (handlers === void 0) {
|
|
1307
|
+
return;
|
|
1308
|
+
}
|
|
1309
|
+
for (const handler of handlers) {
|
|
1310
|
+
try {
|
|
1311
|
+
handler(event.value);
|
|
1312
|
+
} catch (error) {
|
|
1313
|
+
queueMicrotask(() => {
|
|
1314
|
+
throw error;
|
|
1315
|
+
});
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
};
|
|
871
1319
|
const handleResponse = (line) => {
|
|
872
|
-
const
|
|
1320
|
+
const message = JSON.parse(line);
|
|
1321
|
+
if (isDriverEventMessage(message)) {
|
|
1322
|
+
handleEvent(message);
|
|
1323
|
+
return;
|
|
1324
|
+
}
|
|
1325
|
+
const response = message;
|
|
873
1326
|
const entry = pending.get(response.id);
|
|
874
1327
|
if (entry === void 0) {
|
|
875
1328
|
return;
|
|
@@ -944,6 +1397,28 @@ const createDriverSession = (socket, bufferedInput, processState, tempDirectory,
|
|
|
944
1397
|
});
|
|
945
1398
|
});
|
|
946
1399
|
};
|
|
1400
|
+
const subscribe = (channel, scopeId, handler) => {
|
|
1401
|
+
const key = createDriverEventKey(channel, scopeId);
|
|
1402
|
+
const handlers = eventHandlers.get(key) ?? /* @__PURE__ */ new Set();
|
|
1403
|
+
handlers.add(handler);
|
|
1404
|
+
eventHandlers.set(key, handlers);
|
|
1405
|
+
let disposed = false;
|
|
1406
|
+
return {
|
|
1407
|
+
dispose: () => {
|
|
1408
|
+
if (disposed) {
|
|
1409
|
+
return;
|
|
1410
|
+
}
|
|
1411
|
+
disposed = true;
|
|
1412
|
+
handlers.delete(handler);
|
|
1413
|
+
if (handlers.size === 0) {
|
|
1414
|
+
eventHandlers.delete(key);
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
};
|
|
1418
|
+
};
|
|
1419
|
+
const setSystemOutputSink = (sink) => {
|
|
1420
|
+
processState.systemOutputSink = sink;
|
|
1421
|
+
};
|
|
947
1422
|
const waitForExit = async () => {
|
|
948
1423
|
const startedAt = Date.now();
|
|
949
1424
|
while (processState.child.exitCode === null && processState.child.signalCode === null) {
|
|
@@ -1028,10 +1503,10 @@ const createDriverSession = (socket, bufferedInput, processState, tempDirectory,
|
|
|
1028
1503
|
poolOptions.limits
|
|
1029
1504
|
);
|
|
1030
1505
|
};
|
|
1031
|
-
session = { release, request, terminate };
|
|
1506
|
+
session = { release, request, setSystemOutputSink, subscribe, terminate };
|
|
1032
1507
|
return session;
|
|
1033
1508
|
};
|
|
1034
|
-
const startFreshDriverSession = async (effective, xvfb, poolOptions) => {
|
|
1509
|
+
const startFreshDriverSession = async (effective, xvfb, poolOptions, systemOutputSink) => {
|
|
1035
1510
|
const driverPath = resolveDriverPath();
|
|
1036
1511
|
const tempDirectory = node_fs.mkdtempSync(
|
|
1037
1512
|
node_path.join(node_os.tmpdir(), `gestament-launcher-${process.pid}-${socketCounter}-`)
|
|
@@ -1043,7 +1518,13 @@ const startFreshDriverSession = async (effective, xvfb, poolOptions) => {
|
|
|
1043
1518
|
try {
|
|
1044
1519
|
await listenOnSocket(server, socketPath);
|
|
1045
1520
|
server.unref();
|
|
1046
|
-
processState = spawnDriverProcess(
|
|
1521
|
+
processState = spawnDriverProcess(
|
|
1522
|
+
driverPath,
|
|
1523
|
+
socketPath,
|
|
1524
|
+
effective,
|
|
1525
|
+
xvfb,
|
|
1526
|
+
systemOutputSink
|
|
1527
|
+
);
|
|
1047
1528
|
const connection = await waitForDriverReady(server, processState);
|
|
1048
1529
|
server.close();
|
|
1049
1530
|
const resolvedPoolOptions = poolOptions.mode === "all" && xvfb !== void 0 ? {
|
|
@@ -1069,7 +1550,7 @@ const startFreshDriverSession = async (effective, xvfb, poolOptions) => {
|
|
|
1069
1550
|
throw error;
|
|
1070
1551
|
}
|
|
1071
1552
|
};
|
|
1072
|
-
const startDriverSession = async (options) => {
|
|
1553
|
+
const startDriverSession = async (options, systemOutputSink) => {
|
|
1073
1554
|
const display = resolveDisplay(options.display);
|
|
1074
1555
|
const xvfbOptions = resolveXvfbOptions(options);
|
|
1075
1556
|
const effective = resolveEffectiveDisplay(display, xvfbOptions);
|
|
@@ -1077,7 +1558,8 @@ const startDriverSession = async (options) => {
|
|
|
1077
1558
|
return startFreshDriverSession(
|
|
1078
1559
|
effective,
|
|
1079
1560
|
void 0,
|
|
1080
|
-
emptyDriverSessionPoolOptions()
|
|
1561
|
+
emptyDriverSessionPoolOptions(),
|
|
1562
|
+
systemOutputSink
|
|
1081
1563
|
);
|
|
1082
1564
|
}
|
|
1083
1565
|
const pool = effective.xvfb.pool;
|
|
@@ -1085,18 +1567,24 @@ const startDriverSession = async (options) => {
|
|
|
1085
1567
|
return startFreshDriverSession(
|
|
1086
1568
|
effective,
|
|
1087
1569
|
void 0,
|
|
1088
|
-
emptyDriverSessionPoolOptions()
|
|
1570
|
+
emptyDriverSessionPoolOptions(),
|
|
1571
|
+
systemOutputSink
|
|
1089
1572
|
);
|
|
1090
1573
|
}
|
|
1091
1574
|
if (pool.type === "xvfb") {
|
|
1092
|
-
const xvfb2 = await leaseXvfb(effective.xvfb.screen);
|
|
1093
|
-
return startFreshDriverSession(
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1575
|
+
const xvfb2 = await leaseXvfb(effective.xvfb.screen, systemOutputSink);
|
|
1576
|
+
return startFreshDriverSession(
|
|
1577
|
+
effective,
|
|
1578
|
+
xvfb2,
|
|
1579
|
+
{
|
|
1580
|
+
allKey: void 0,
|
|
1581
|
+
allowedMappedWindowCount: 0,
|
|
1582
|
+
limits: poolLimits(pool),
|
|
1583
|
+
mode: "xvfb",
|
|
1584
|
+
xvfb: xvfb2
|
|
1585
|
+
},
|
|
1586
|
+
systemOutputSink
|
|
1587
|
+
);
|
|
1100
1588
|
}
|
|
1101
1589
|
const key = allPoolKey(effective.xvfb);
|
|
1102
1590
|
for (; ; ) {
|
|
@@ -1107,20 +1595,27 @@ const startDriverSession = async (options) => {
|
|
|
1107
1595
|
if (idle.xvfb.child.exitCode === null && idle.xvfb.child.signalCode === null) {
|
|
1108
1596
|
idle.lastUsedAt = Date.now();
|
|
1109
1597
|
idle.xvfb.lastUsedAt = idle.lastUsedAt;
|
|
1598
|
+
idle.xvfb.systemOutputSink = systemOutputSink;
|
|
1599
|
+
idle.session.setSystemOutputSink(systemOutputSink);
|
|
1110
1600
|
return idle.session;
|
|
1111
1601
|
}
|
|
1112
1602
|
await idle.session.terminate().catch(() => void 0);
|
|
1113
1603
|
}
|
|
1114
|
-
const xvfb = await leaseXvfb(effective.xvfb.screen);
|
|
1115
|
-
return startFreshDriverSession(
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1604
|
+
const xvfb = await leaseXvfb(effective.xvfb.screen, systemOutputSink);
|
|
1605
|
+
return startFreshDriverSession(
|
|
1606
|
+
effective,
|
|
1607
|
+
xvfb,
|
|
1608
|
+
{
|
|
1609
|
+
allKey: key,
|
|
1610
|
+
allowedMappedWindowCount: 0,
|
|
1611
|
+
limits: poolLimits(pool),
|
|
1612
|
+
mode: "all",
|
|
1613
|
+
xvfb
|
|
1614
|
+
},
|
|
1615
|
+
systemOutputSink
|
|
1616
|
+
);
|
|
1122
1617
|
};
|
|
1123
|
-
const createLaunchPayload = (options, args) => {
|
|
1618
|
+
const createLaunchPayload = (options, args, launchOptions, outputScopeId) => {
|
|
1124
1619
|
const display = resolveDisplay(options.display);
|
|
1125
1620
|
const xvfb = resolveXvfbOptions(options);
|
|
1126
1621
|
const effective = resolveEffectiveDisplay(display, xvfb);
|
|
@@ -1128,6 +1623,11 @@ const createLaunchPayload = (options, args) => {
|
|
|
1128
1623
|
appPath: options.appPath,
|
|
1129
1624
|
args: [...options.args ?? [], ...args],
|
|
1130
1625
|
env: resolveLauncherEnvironment(options, effective),
|
|
1626
|
+
outputBufferBytes: resolveOutputBufferBytes(
|
|
1627
|
+
options.outputBufferBytes,
|
|
1628
|
+
launchOptions?.outputBufferBytes
|
|
1629
|
+
),
|
|
1630
|
+
outputScopeId,
|
|
1131
1631
|
timeoutMs: options.timeoutMs ?? null
|
|
1132
1632
|
};
|
|
1133
1633
|
};
|
|
@@ -1141,7 +1641,7 @@ const createEnvironmentPayload = (options) => {
|
|
|
1141
1641
|
};
|
|
1142
1642
|
const elementRefToProxy = (session, ref) => ref === null ? void 0 : createProxyGtkElement(session, ref);
|
|
1143
1643
|
const trayRefToProxy = (session, ref) => ref === null ? void 0 : createProxyGtkTrayItem(session, ref);
|
|
1144
|
-
const createProxyGtkApp = (session, ref) => {
|
|
1644
|
+
const createProxyGtkApp = (session, ref, outputSubscription) => {
|
|
1145
1645
|
let released = false;
|
|
1146
1646
|
const assertNotReleased = () => {
|
|
1147
1647
|
if (released) {
|
|
@@ -1160,6 +1660,7 @@ const createProxyGtkApp = (session, ref) => {
|
|
|
1160
1660
|
return;
|
|
1161
1661
|
}
|
|
1162
1662
|
released = true;
|
|
1663
|
+
outputSubscription?.dispose();
|
|
1163
1664
|
await session.request("app.release", { appId: ref.appId });
|
|
1164
1665
|
};
|
|
1165
1666
|
const app = {
|
|
@@ -1167,6 +1668,7 @@ const createProxyGtkApp = (session, ref) => {
|
|
|
1167
1668
|
environment: async () => wireEnvironmentToGtkAppEnvironment(
|
|
1168
1669
|
await appRequest("app.environment")
|
|
1169
1670
|
),
|
|
1671
|
+
output: () => appRequest("app.output"),
|
|
1170
1672
|
findById: async (id) => elementRefToProxy(
|
|
1171
1673
|
session,
|
|
1172
1674
|
await appRequest("app.findById", { id })
|
|
@@ -1280,9 +1782,23 @@ const createProxyGtkElement = (session, ref) => {
|
|
|
1280
1782
|
switch (ref.kind) {
|
|
1281
1783
|
case "window":
|
|
1282
1784
|
target.bounds = () => session.request("element.bounds", { elementId });
|
|
1785
|
+
target.moveTo = (x, y) => session.request("window.moveTo", {
|
|
1786
|
+
elementId,
|
|
1787
|
+
x,
|
|
1788
|
+
y
|
|
1789
|
+
});
|
|
1283
1790
|
target.resizeHints = () => session.request("window.resizeHints", {
|
|
1284
1791
|
elementId
|
|
1285
1792
|
});
|
|
1793
|
+
target.resizeTo = (width, height) => session.request("window.resizeTo", {
|
|
1794
|
+
elementId,
|
|
1795
|
+
height,
|
|
1796
|
+
width
|
|
1797
|
+
});
|
|
1798
|
+
target.setBounds = (bounds) => session.request("window.setBounds", {
|
|
1799
|
+
bounds,
|
|
1800
|
+
elementId
|
|
1801
|
+
});
|
|
1286
1802
|
target.x11Info = () => session.request("window.x11Info", { elementId });
|
|
1287
1803
|
addChildContainerProxyOperations(session, elementId, target);
|
|
1288
1804
|
break;
|
|
@@ -1394,19 +1910,72 @@ const createProxyGtkTrayItem = (session, ref) => {
|
|
|
1394
1910
|
};
|
|
1395
1911
|
};
|
|
1396
1912
|
const createDriverBackedGtkAppLauncher = (options) => {
|
|
1913
|
+
const outputSubscriptions = /* @__PURE__ */ new Set();
|
|
1914
|
+
let systemOutputRecorder = createGtkSystemOutputRecorder(
|
|
1915
|
+
normalizeOutputBufferBytes(
|
|
1916
|
+
options.systemOutputBufferBytes,
|
|
1917
|
+
"systemOutputBufferBytes"
|
|
1918
|
+
)
|
|
1919
|
+
);
|
|
1397
1920
|
let sessionPromise;
|
|
1398
1921
|
const ensureSession = () => {
|
|
1399
|
-
sessionPromise
|
|
1922
|
+
if (sessionPromise === void 0) {
|
|
1923
|
+
systemOutputRecorder = createGtkSystemOutputRecorder(
|
|
1924
|
+
normalizeOutputBufferBytes(
|
|
1925
|
+
options.systemOutputBufferBytes,
|
|
1926
|
+
"systemOutputBufferBytes"
|
|
1927
|
+
)
|
|
1928
|
+
);
|
|
1929
|
+
sessionPromise = startDriverSession(
|
|
1930
|
+
options,
|
|
1931
|
+
createSystemOutputSink(systemOutputRecorder, options.onSystemOutput)
|
|
1932
|
+
);
|
|
1933
|
+
}
|
|
1400
1934
|
return sessionPromise;
|
|
1401
1935
|
};
|
|
1402
|
-
const
|
|
1403
|
-
|
|
1936
|
+
const trackOutputSubscription = (subscription) => {
|
|
1937
|
+
let tracked;
|
|
1938
|
+
tracked = {
|
|
1939
|
+
dispose: () => {
|
|
1940
|
+
if (!outputSubscriptions.delete(tracked)) {
|
|
1941
|
+
return;
|
|
1942
|
+
}
|
|
1943
|
+
subscription.dispose();
|
|
1944
|
+
}
|
|
1945
|
+
};
|
|
1946
|
+
outputSubscriptions.add(tracked);
|
|
1947
|
+
return tracked;
|
|
1948
|
+
};
|
|
1949
|
+
const disposeOutputSubscriptions = () => {
|
|
1950
|
+
for (const subscription of [...outputSubscriptions]) {
|
|
1951
|
+
subscription.dispose();
|
|
1952
|
+
}
|
|
1953
|
+
};
|
|
1954
|
+
const launch = async (args, launchOptions) => {
|
|
1404
1955
|
const session = await ensureSession();
|
|
1405
|
-
const
|
|
1406
|
-
|
|
1407
|
-
|
|
1956
|
+
const onOutput = launchOptions?.onOutput;
|
|
1957
|
+
const outputScopeId = onOutput === void 0 ? null : createOutputScopeId();
|
|
1958
|
+
const outputSubscription = outputScopeId === null ? void 0 : trackOutputSubscription(
|
|
1959
|
+
session.subscribe("app.output", outputScopeId, (value) => {
|
|
1960
|
+
onOutput?.(value);
|
|
1961
|
+
})
|
|
1408
1962
|
);
|
|
1409
|
-
|
|
1963
|
+
const payload = createLaunchPayload(
|
|
1964
|
+
options,
|
|
1965
|
+
args ?? [],
|
|
1966
|
+
launchOptions,
|
|
1967
|
+
outputScopeId
|
|
1968
|
+
);
|
|
1969
|
+
try {
|
|
1970
|
+
const appRef = await session.request(
|
|
1971
|
+
"launcher.launch",
|
|
1972
|
+
payload
|
|
1973
|
+
);
|
|
1974
|
+
return createProxyGtkApp(session, appRef, outputSubscription);
|
|
1975
|
+
} catch (error) {
|
|
1976
|
+
outputSubscription?.dispose();
|
|
1977
|
+
throw error;
|
|
1978
|
+
}
|
|
1410
1979
|
};
|
|
1411
1980
|
const environment = async () => {
|
|
1412
1981
|
const payload = createEnvironmentPayload(options);
|
|
@@ -1418,6 +1987,7 @@ const createDriverBackedGtkAppLauncher = (options) => {
|
|
|
1418
1987
|
)
|
|
1419
1988
|
);
|
|
1420
1989
|
};
|
|
1990
|
+
const systemOutput = () => Promise.resolve(systemOutputRecorder.snapshot());
|
|
1421
1991
|
const release = async () => {
|
|
1422
1992
|
const releasingSession = sessionPromise;
|
|
1423
1993
|
sessionPromise = void 0;
|
|
@@ -1425,12 +1995,17 @@ const createDriverBackedGtkAppLauncher = (options) => {
|
|
|
1425
1995
|
return;
|
|
1426
1996
|
}
|
|
1427
1997
|
const session = await releasingSession;
|
|
1428
|
-
|
|
1998
|
+
try {
|
|
1999
|
+
await session.release();
|
|
2000
|
+
} finally {
|
|
2001
|
+
disposeOutputSubscriptions();
|
|
2002
|
+
}
|
|
1429
2003
|
};
|
|
1430
2004
|
return {
|
|
1431
2005
|
environment,
|
|
1432
2006
|
launch,
|
|
1433
2007
|
release,
|
|
2008
|
+
systemOutput,
|
|
1434
2009
|
[Symbol.asyncDispose]: release
|
|
1435
2010
|
};
|
|
1436
2011
|
};
|
|
@@ -1446,6 +2021,19 @@ const assertFiniteNumber = (name, value) => {
|
|
|
1446
2021
|
throw errors.createGtkInvalidArgumentError(`${name} must be a finite number.`);
|
|
1447
2022
|
}
|
|
1448
2023
|
};
|
|
2024
|
+
const int32Min = -2147483648;
|
|
2025
|
+
const int32Max = 2147483647;
|
|
2026
|
+
const assertInt32 = (name, value) => {
|
|
2027
|
+
if (!Number.isInteger(value) || value < int32Min || value > int32Max) {
|
|
2028
|
+
throw errors.createGtkInvalidArgumentError(`${name} must be a 32-bit integer.`);
|
|
2029
|
+
}
|
|
2030
|
+
};
|
|
2031
|
+
const assertPositiveInt32 = (name, value) => {
|
|
2032
|
+
assertInt32(name, value);
|
|
2033
|
+
if (value <= 0) {
|
|
2034
|
+
throw errors.createGtkInvalidArgumentError(`${name} must be greater than zero.`);
|
|
2035
|
+
}
|
|
2036
|
+
};
|
|
1449
2037
|
const normalizeRoleName = (roleName) => roleName.trim().toLowerCase().replace(/[_-]+/g, " ").replace(/\s+/g, " ");
|
|
1450
2038
|
const hasInterface = (info, name) => info.interfaces.some(
|
|
1451
2039
|
(interfaceName) => interfaceName.toLowerCase() === name.toLowerCase()
|
|
@@ -1838,6 +2426,26 @@ const createImageInfoOperation = (handle) => async () => {
|
|
|
1838
2426
|
};
|
|
1839
2427
|
};
|
|
1840
2428
|
const createBoundsOperation = (handle) => async () => native.nativeBounds(handle);
|
|
2429
|
+
const createMoveToOperation = (handle) => async (x, y) => {
|
|
2430
|
+
assertInt32("x", x);
|
|
2431
|
+
assertInt32("y", y);
|
|
2432
|
+
return native.nativeMoveWindow(handle, x, y);
|
|
2433
|
+
};
|
|
2434
|
+
const createResizeToOperation = (handle) => async (width, height) => {
|
|
2435
|
+
assertPositiveInt32("width", width);
|
|
2436
|
+
assertPositiveInt32("height", height);
|
|
2437
|
+
return native.nativeResizeWindow(handle, width, height);
|
|
2438
|
+
};
|
|
2439
|
+
const createSetBoundsOperation = (handle) => async (bounds) => {
|
|
2440
|
+
if (typeof bounds !== "object" || bounds === null) {
|
|
2441
|
+
throw errors.createGtkInvalidArgumentError("bounds must be an object.");
|
|
2442
|
+
}
|
|
2443
|
+
assertInt32("bounds.x", bounds.x);
|
|
2444
|
+
assertInt32("bounds.y", bounds.y);
|
|
2445
|
+
assertPositiveInt32("bounds.width", bounds.width);
|
|
2446
|
+
assertPositiveInt32("bounds.height", bounds.height);
|
|
2447
|
+
return native.nativeSetWindowBounds(handle, bounds);
|
|
2448
|
+
};
|
|
1841
2449
|
const createResizeHintsOperation = (handle) => async () => native.nativeResizeHints(handle);
|
|
1842
2450
|
const createX11InfoOperation = (handle) => async () => native.nativeX11Info(handle);
|
|
1843
2451
|
const createValueOperation = (handle) => async () => native.nativeValueInfo(handle).value;
|
|
@@ -2012,7 +2620,10 @@ const createGtkElement = (handle) => {
|
|
|
2012
2620
|
...common,
|
|
2013
2621
|
kind: "window",
|
|
2014
2622
|
bounds: createBoundsOperation(handle),
|
|
2623
|
+
moveTo: createMoveToOperation(handle),
|
|
2015
2624
|
...createChildContainerOperations(handle, void 0),
|
|
2625
|
+
resizeTo: createResizeToOperation(handle),
|
|
2626
|
+
setBounds: createSetBoundsOperation(handle),
|
|
2016
2627
|
resizeHints: createResizeHintsOperation(handle),
|
|
2017
2628
|
x11Info: createX11InfoOperation(handle)
|
|
2018
2629
|
};
|
|
@@ -2342,31 +2953,70 @@ const createGtkAppEnvironment = (baseEnv, overrides) => {
|
|
|
2342
2953
|
const launchGtkApp = (appPath, args, options) => {
|
|
2343
2954
|
const _args = args ?? [];
|
|
2344
2955
|
const _timeoutMs = options?.timeoutMs ?? 1e4;
|
|
2956
|
+
const outputBufferBytes = normalizeOutputBufferBytes(
|
|
2957
|
+
options?.outputBufferBytes
|
|
2958
|
+
);
|
|
2959
|
+
const outputRecorder = createGtkAppOutputRecorder(outputBufferBytes);
|
|
2345
2960
|
const appEnvironment = createGtkAppEnvironment(process.env, options?.env);
|
|
2346
2961
|
const child = node_child_process.spawn(appPath, [..._args], {
|
|
2347
2962
|
env: appEnvironment,
|
|
2348
2963
|
stdio: "pipe"
|
|
2349
2964
|
});
|
|
2965
|
+
let resolveClosed;
|
|
2966
|
+
const closedPromise = new Promise((resolve) => {
|
|
2967
|
+
resolveClosed = resolve;
|
|
2968
|
+
});
|
|
2350
2969
|
const state = {
|
|
2351
2970
|
atspiReadiness: "missing-bus-name",
|
|
2352
2971
|
atspiReady: false,
|
|
2972
|
+
closed: false,
|
|
2973
|
+
closedPromise,
|
|
2353
2974
|
exitCode: null,
|
|
2354
2975
|
exitSignal: null,
|
|
2355
2976
|
process: child,
|
|
2977
|
+
released: false,
|
|
2356
2978
|
stderr: [],
|
|
2357
2979
|
stdout: []
|
|
2358
2980
|
};
|
|
2981
|
+
const notifyOutput = (stream, chunk) => {
|
|
2982
|
+
notifyGtkAppOutput(options?.onOutput, outputRecorder.append(stream, chunk));
|
|
2983
|
+
};
|
|
2984
|
+
const flushOutput = (stream) => {
|
|
2985
|
+
notifyGtkAppOutput(options?.onOutput, outputRecorder.flush(stream));
|
|
2986
|
+
};
|
|
2359
2987
|
child.stdout.on("data", (chunk) => {
|
|
2360
2988
|
appendOutput(state.stdout, chunk);
|
|
2989
|
+
notifyOutput("stdout", chunk);
|
|
2361
2990
|
});
|
|
2362
2991
|
child.stderr.on("data", (chunk) => {
|
|
2363
2992
|
appendOutput(state.stderr, chunk);
|
|
2993
|
+
notifyOutput("stderr", chunk);
|
|
2994
|
+
});
|
|
2995
|
+
child.stdout.on("end", () => {
|
|
2996
|
+
flushOutput("stdout");
|
|
2997
|
+
});
|
|
2998
|
+
child.stderr.on("end", () => {
|
|
2999
|
+
flushOutput("stderr");
|
|
2364
3000
|
});
|
|
2365
3001
|
child.on("exit", (code, signal) => {
|
|
2366
3002
|
state.exitCode = code;
|
|
2367
3003
|
state.exitSignal = signal;
|
|
2368
3004
|
});
|
|
3005
|
+
child.on("close", (code, signal) => {
|
|
3006
|
+
if (state.exitCode === null && state.exitSignal === null) {
|
|
3007
|
+
state.exitCode = code;
|
|
3008
|
+
state.exitSignal = signal;
|
|
3009
|
+
}
|
|
3010
|
+
flushOutput("stdout");
|
|
3011
|
+
flushOutput("stderr");
|
|
3012
|
+
state.closed = true;
|
|
3013
|
+
resolveClosed?.();
|
|
3014
|
+
});
|
|
2369
3015
|
const release = async () => {
|
|
3016
|
+
if (state.released) {
|
|
3017
|
+
return;
|
|
3018
|
+
}
|
|
3019
|
+
state.released = true;
|
|
2370
3020
|
if (state.exitCode !== null || state.exitSignal !== null) {
|
|
2371
3021
|
return;
|
|
2372
3022
|
}
|
|
@@ -2380,6 +3030,11 @@ const launchGtkApp = (appPath, args, options) => {
|
|
|
2380
3030
|
await wait.delay(25);
|
|
2381
3031
|
}
|
|
2382
3032
|
};
|
|
3033
|
+
const assertNotReleased = () => {
|
|
3034
|
+
if (state.released) {
|
|
3035
|
+
throw errors.createGtkAppExitedError("GTK application has been released.");
|
|
3036
|
+
}
|
|
3037
|
+
};
|
|
2383
3038
|
const findById = async (id) => {
|
|
2384
3039
|
const startedAt = Date.now();
|
|
2385
3040
|
const timeoutMs = wait.effectiveWaitTimeoutMs(_timeoutMs);
|
|
@@ -2487,6 +3142,7 @@ const launchGtkApp = (appPath, args, options) => {
|
|
|
2487
3142
|
};
|
|
2488
3143
|
const app = {
|
|
2489
3144
|
capture: async () => {
|
|
3145
|
+
assertNotReleased();
|
|
2490
3146
|
assertProcessRunning(state, appPath);
|
|
2491
3147
|
try {
|
|
2492
3148
|
return native.nativeCaptureScreen();
|
|
@@ -2495,9 +3151,20 @@ const launchGtkApp = (appPath, args, options) => {
|
|
|
2495
3151
|
}
|
|
2496
3152
|
},
|
|
2497
3153
|
environment: async () => {
|
|
3154
|
+
assertNotReleased();
|
|
2498
3155
|
assertProcessRunning(state, appPath);
|
|
2499
3156
|
return { ...appEnvironment };
|
|
2500
3157
|
},
|
|
3158
|
+
output: async () => {
|
|
3159
|
+
assertNotReleased();
|
|
3160
|
+
if ((state.exitCode !== null || state.exitSignal !== null) && !state.closed) {
|
|
3161
|
+
await state.closedPromise;
|
|
3162
|
+
}
|
|
3163
|
+
return outputRecorder.snapshot(
|
|
3164
|
+
state.exitCode,
|
|
3165
|
+
state.exitSignal === null ? null : state.exitSignal
|
|
3166
|
+
);
|
|
3167
|
+
},
|
|
2501
3168
|
findById,
|
|
2502
3169
|
findByPath,
|
|
2503
3170
|
getById,
|
|
@@ -2564,4 +3231,4 @@ const createGtkAppLauncher = (options) => createDriverBackedGtkAppLauncher(optio
|
|
|
2564
3231
|
exports.createGtkAppEnvironment = createGtkAppEnvironment;
|
|
2565
3232
|
exports.createGtkAppLauncher = createGtkAppLauncher;
|
|
2566
3233
|
exports.launchGtkApp = launchGtkApp;
|
|
2567
|
-
//# sourceMappingURL=launchGtkApp-
|
|
3234
|
+
//# sourceMappingURL=launchGtkApp-OggUtTTC.cjs.map
|