gestament 0.3.0 → 0.5.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.
Files changed (77) hide show
  1. package/README.md +22 -5
  2. package/dist/displaySession.d.ts +2 -2
  3. package/dist/displaySession.d.ts.map +1 -1
  4. package/dist/element.d.ts +2 -2
  5. package/dist/element.d.ts.map +1 -1
  6. package/dist/errors.d.ts +2 -2
  7. package/dist/generated/packageMetadata.d.ts +4 -4
  8. package/dist/gestament-config.d.ts +2 -2
  9. package/dist/gestament-launcher-driver.cjs +118 -5
  10. package/dist/gestament-launcher-driver.cjs.map +1 -1
  11. package/dist/gestament-launcher-driver.d.ts +2 -2
  12. package/dist/gestament-launcher-driver.mjs +118 -5
  13. package/dist/gestament-launcher-driver.mjs.map +1 -1
  14. package/dist/gestament-tray-host.cjs +9 -1
  15. package/dist/gestament-tray-host.cjs.map +1 -1
  16. package/dist/gestament-tray-host.d.ts +2 -2
  17. package/dist/gestament-tray-host.mjs +9 -1
  18. package/dist/gestament-tray-host.mjs.map +1 -1
  19. package/dist/gestament-xvfb-pool-probe.cjs +1 -1
  20. package/dist/gestament-xvfb-pool-probe.d.ts +2 -2
  21. package/dist/gestament-xvfb-pool-probe.mjs +1 -1
  22. package/dist/gestament-xvfb-worker.d.ts +2 -2
  23. package/dist/gestament-xvfb.cjs +7 -2
  24. package/dist/gestament-xvfb.cjs.map +1 -1
  25. package/dist/gestament-xvfb.d.ts +2 -2
  26. package/dist/gestament-xvfb.mjs +7 -2
  27. package/dist/gestament-xvfb.mjs.map +1 -1
  28. package/dist/gestament.cjs +279 -0
  29. package/dist/gestament.cjs.map +1 -0
  30. package/dist/gestament.d.ts +29 -0
  31. package/dist/gestament.d.ts.map +1 -0
  32. package/dist/gestament.mjs +278 -0
  33. package/dist/gestament.mjs.map +1 -0
  34. package/dist/index.cjs +1 -1
  35. package/dist/index.d.ts +2 -2
  36. package/dist/index.mjs +1 -1
  37. package/dist/{launchGtkApp-BIO_5Xjn.cjs → launchGtkApp-CzYcrc9f.cjs} +794 -89
  38. package/dist/launchGtkApp-CzYcrc9f.cjs.map +1 -0
  39. package/dist/{launchGtkApp-qi1qm5G4.js → launchGtkApp-EI6OIpPI.js} +792 -87
  40. package/dist/launchGtkApp-EI6OIpPI.js.map +1 -0
  41. package/dist/launchGtkApp.d.ts +2 -2
  42. package/dist/launchGtkApp.d.ts.map +1 -1
  43. package/dist/launcherDriverProtocol.d.ts +29 -4
  44. package/dist/launcherDriverProtocol.d.ts.map +1 -1
  45. package/dist/{native-CWdUmdty.js → native-DlCBBWlV.js} +16 -10
  46. package/dist/native-DlCBBWlV.js.map +1 -0
  47. package/dist/{native-CBXaFWP-.cjs → native-Kfm95Uxw.cjs} +12 -6
  48. package/dist/native-Kfm95Uxw.cjs.map +1 -0
  49. package/dist/native.d.ts +23 -2
  50. package/dist/native.d.ts.map +1 -1
  51. package/dist/output.d.ts +27 -0
  52. package/dist/output.d.ts.map +1 -0
  53. package/dist/packageMetadata-CewXH0iF.cjs +4 -0
  54. package/dist/packageMetadata-CewXH0iF.cjs.map +1 -0
  55. package/dist/packageMetadata-DjxyJCJm.js +5 -0
  56. package/dist/packageMetadata-DjxyJCJm.js.map +1 -0
  57. package/dist/prerequisites.d.ts +2 -2
  58. package/dist/testing.d.ts +2 -2
  59. package/dist/tray.d.ts +2 -2
  60. package/dist/types.d.ts +277 -3
  61. package/dist/types.d.ts.map +1 -1
  62. package/dist/wait.d.ts +2 -2
  63. package/package.json +8 -7
  64. package/prebuilds/linux-arm/gtk3/node.napi.armv7.glibc.node +0 -0
  65. package/prebuilds/linux-arm/gtk4/node.napi.armv7.glibc.node +0 -0
  66. package/prebuilds/linux-arm64/gtk3/node.napi.glibc.node +0 -0
  67. package/prebuilds/linux-arm64/gtk4/node.napi.glibc.node +0 -0
  68. package/prebuilds/linux-ia32/gtk3/node.napi.glibc.node +0 -0
  69. package/prebuilds/linux-ia32/gtk4/node.napi.glibc.node +0 -0
  70. package/prebuilds/linux-riscv64/gtk3/node.napi.glibc.node +0 -0
  71. package/prebuilds/linux-riscv64/gtk4/node.napi.glibc.node +0 -0
  72. package/prebuilds/linux-x64/gtk3/node.napi.glibc.node +0 -0
  73. package/prebuilds/linux-x64/gtk4/node.napi.glibc.node +0 -0
  74. package/dist/launchGtkApp-BIO_5Xjn.cjs.map +0 -1
  75. package/dist/launchGtkApp-qi1qm5G4.js.map +0 -1
  76. package/dist/native-CBXaFWP-.cjs.map +0 -1
  77. package/dist/native-CWdUmdty.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-CBXaFWP-.cjs");
11
+ const native = require("./native-Kfm95Uxw.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,8 +247,23 @@ 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;
255
+ const sessionOwnedEnvironmentKeys = [
256
+ "DISPLAY",
257
+ "WAYLAND_DISPLAY",
258
+ "GDK_BACKEND",
259
+ "DBUS_SESSION_BUS_ADDRESS",
260
+ "AT_SPI_BUS_ADDRESS",
261
+ "NO_AT_BRIDGE",
262
+ "XAUTHORITY",
263
+ "GESTAMENT_XVFB_ACTIVE",
264
+ "XDG_SESSION_TYPE"
265
+ ];
266
+ let outputScopeCounter = 0;
27
267
  let socketCounter = 0;
28
268
  const leasedDisplayNumbers = /* @__PURE__ */ new Set();
29
269
  const idleXvfbByKey = /* @__PURE__ */ new Map();
@@ -40,6 +280,12 @@ const appendOutput$1 = (lines, chunk) => {
40
280
  lines.splice(0, lines.length - 40);
41
281
  }
42
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
+ };
43
289
  const unrefHandle = (handle) => {
44
290
  const maybeRefHandle = handle;
45
291
  if (typeof maybeRefHandle.unref === "function") {
@@ -58,6 +304,41 @@ ${stdoutText}
58
304
  stderr:
59
305
  ${stderrText}`;
60
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;
61
342
  const getHostDisplayState = () => ({
62
343
  display: process.env.DISPLAY,
63
344
  waylandDisplay: process.env.WAYLAND_DISPLAY
@@ -83,6 +364,63 @@ const resolveDisplay = (display) => {
83
364
  );
84
365
  };
85
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
+ };
86
424
  const resolvePoolLimit = (name, value, defaultValue) => {
87
425
  if (value === void 0) {
88
426
  return defaultValue;
@@ -230,6 +568,7 @@ const retainIdleXvfb = async (xvfb, limits) => {
230
568
  await terminateXvfb(xvfb);
231
569
  return;
232
570
  }
571
+ xvfb.systemOutputSink = void 0;
233
572
  xvfb.lastUsedAt = Date.now();
234
573
  pushArrayEntry(idleXvfbByKey, xvfb.key, xvfb);
235
574
  await trimIdleXvfbKey(xvfb.key, limits.maxIdlePerKey);
@@ -240,6 +579,8 @@ const retainIdleAllSession = async (session, limits) => {
240
579
  await session.session.terminate();
241
580
  return;
242
581
  }
582
+ session.session.setSystemOutputSink(void 0);
583
+ session.xvfb.systemOutputSink = void 0;
243
584
  session.lastUsedAt = Date.now();
244
585
  session.xvfb.lastUsedAt = session.lastUsedAt;
245
586
  pushArrayEntry(idleAllByKey, session.key, session);
@@ -313,15 +654,37 @@ const toWireEnvironment = (env) => {
313
654
  }
314
655
  return wireEnv;
315
656
  };
316
- const resolveLauncherEnvironment = (options, effective) => toWireEnvironment({
317
- GDK_BACKEND: resolveGdkBackend(effective),
318
- GSETTINGS_BACKEND: options.gsettings === null ? void 0 : options.gsettings ?? defaultGSettings,
319
- GTK_THEME: options.theme === null ? void 0 : options.theme ?? defaultTheme,
320
- ...options.env
321
- });
657
+ const wireEnvironmentToGtkAppEnvironment = (env) => {
658
+ const appEnv = {};
659
+ for (const [key, value] of Object.entries(env)) {
660
+ appEnv[key] = value === null ? void 0 : value;
661
+ }
662
+ return appEnv;
663
+ };
664
+ const assertNoSessionOwnedEnvironmentOverrides = (options, effective) => {
665
+ if (effective.kind !== "xvfb" || options.env === void 0) {
666
+ return;
667
+ }
668
+ for (const key of sessionOwnedEnvironmentKeys) {
669
+ if (Object.hasOwn(options.env, key)) {
670
+ throw errors.createGtkInvalidArgumentError(
671
+ `options.env must not override ${key} when using internal Xvfb.`
672
+ );
673
+ }
674
+ }
675
+ };
676
+ const resolveLauncherEnvironment = (options, effective) => {
677
+ assertNoSessionOwnedEnvironmentOverrides(options, effective);
678
+ return toWireEnvironment({
679
+ GDK_BACKEND: resolveGdkBackend(effective),
680
+ GSETTINGS_BACKEND: options.gsettings === null ? void 0 : options.gsettings ?? defaultGSettings,
681
+ GTK_THEME: options.theme === null ? void 0 : options.theme ?? defaultTheme,
682
+ ...options.env
683
+ });
684
+ };
322
685
  const resolveDriverPath = () => {
323
686
  const driverPath = node_path.resolve(
324
- node_path.dirname(node_url.fileURLToPath(typeof document === "undefined" ? require("url").pathToFileURL(__filename).href : _documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === "SCRIPT" && _documentCurrentScript.src || new URL("launchGtkApp-BIO_5Xjn.cjs", document.baseURI).href)),
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-CzYcrc9f.cjs", document.baseURI).href)),
325
688
  "..",
326
689
  "dist",
327
690
  "gestament-launcher-driver.cjs"
@@ -335,7 +698,7 @@ const resolveDriverPath = () => {
335
698
  };
336
699
  const resolveXvfbPoolProbePath = () => {
337
700
  const probePath = node_path.resolve(
338
- node_path.dirname(node_url.fileURLToPath(typeof document === "undefined" ? require("url").pathToFileURL(__filename).href : _documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === "SCRIPT" && _documentCurrentScript.src || new URL("launchGtkApp-BIO_5Xjn.cjs", document.baseURI).href)),
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-CzYcrc9f.cjs", document.baseURI).href)),
339
702
  "..",
340
703
  "dist",
341
704
  "gestament-xvfb-pool-probe.cjs"
@@ -352,11 +715,17 @@ const createDriverEnvironment = (effective, xvfb) => {
352
715
  delete env.AT_SPI_BUS_ADDRESS;
353
716
  delete env.NO_AT_BRIDGE;
354
717
  if (effective.kind === "xvfb") {
718
+ delete env.DBUS_SESSION_BUS_ADDRESS;
719
+ delete env.DISPLAY;
720
+ delete env.WAYLAND_DISPLAY;
721
+ delete env.AT_SPI_BUS_ADDRESS;
722
+ delete env.NO_AT_BRIDGE;
723
+ delete env.XAUTHORITY;
355
724
  env.GDK_BACKEND = "x11";
356
725
  env.GESTAMENT_XVFB_ACTIVE = "1";
726
+ env.XDG_SESSION_TYPE = "x11";
357
727
  if (xvfb !== void 0) {
358
728
  env.DISPLAY = xvfb.display;
359
- delete env.XAUTHORITY;
360
729
  }
361
730
  }
362
731
  return env;
@@ -424,7 +793,7 @@ const installPoolCleanup = () => {
424
793
  }
425
794
  });
426
795
  };
427
- const spawnDirectXvfb = async (screen) => {
796
+ const spawnDirectXvfb = async (screen, systemOutputSink) => {
428
797
  installPoolCleanup();
429
798
  for (let displayNumber = firstPooledDisplayNumber; displayNumber <= lastPooledDisplayNumber; displayNumber += 1) {
430
799
  if (!isDisplayNumberAvailable(displayNumber)) {
@@ -441,11 +810,30 @@ const spawnDirectXvfb = async (screen) => {
441
810
  stdio: ["ignore", "pipe", "pipe"]
442
811
  }
443
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
+ };
444
824
  child.stdout.on("data", (chunk) => {
445
825
  appendOutput$1(stdout, chunk);
826
+ appendSystemOutput(xvfb.systemOutputSink, "xvfb", "stdout", chunk);
446
827
  });
447
828
  child.stderr.on("data", (chunk) => {
448
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");
449
837
  });
450
838
  child.unref();
451
839
  unrefHandle(child.stdout);
@@ -468,22 +856,10 @@ const spawnDirectXvfb = async (screen) => {
468
856
  });
469
857
  })
470
858
  ]);
471
- const xvfb = {
472
- child,
473
- display: `:${displayNumber}`,
474
- displayNumber,
475
- key: screen,
476
- lastUsedAt: Date.now(),
477
- screen,
478
- stderr,
479
- stdout
480
- };
481
859
  directXvfbs.add(xvfb);
482
860
  return xvfb;
483
861
  } catch (error) {
484
- killXvfbNow({
485
- child
486
- });
862
+ killXvfbNow(xvfb);
487
863
  leasedDisplayNumbers.delete(displayNumber);
488
864
  const message = error instanceof Error ? error.message : String(error);
489
865
  if (message.includes("ENOENT")) {
@@ -511,39 +887,69 @@ const terminateXvfb = async (xvfb) => {
511
887
  await delay(25);
512
888
  }
513
889
  }
890
+ xvfb.systemOutputSink = void 0;
514
891
  leasedDisplayNumbers.delete(xvfb.displayNumber);
515
892
  };
516
- const leaseXvfb = async (screen) => {
893
+ const leaseXvfb = async (screen, systemOutputSink) => {
517
894
  for (; ; ) {
518
895
  const idle = popArrayEntry(idleXvfbByKey, screen);
519
896
  if (idle === void 0) {
520
- return spawnDirectXvfb(screen);
897
+ return spawnDirectXvfb(screen, systemOutputSink);
521
898
  }
522
899
  if (idle.child.exitCode === null && idle.child.signalCode === null) {
523
900
  idle.lastUsedAt = Date.now();
901
+ idle.systemOutputSink = systemOutputSink;
524
902
  return idle;
525
903
  }
526
904
  await terminateXvfb(idle);
527
905
  }
528
906
  };
529
- const runXvfbProbe = (xvfb) => new Promise((resolveProbe, rejectProbe) => {
907
+ const runXvfbProbeOnce = (xvfb, timeoutMs) => new Promise((resolveProbe, rejectProbe) => {
530
908
  const probePath = resolveXvfbPoolProbePath();
531
909
  const stdout = [];
532
910
  const stderr = [];
911
+ const env = {
912
+ ...process.env,
913
+ DISPLAY: xvfb.display,
914
+ GDK_BACKEND: "x11",
915
+ GESTAMENT_XVFB_ACTIVE: "1",
916
+ XDG_SESSION_TYPE: "x11"
917
+ };
918
+ delete env.AT_SPI_BUS_ADDRESS;
919
+ delete env.DBUS_SESSION_BUS_ADDRESS;
920
+ delete env.NO_AT_BRIDGE;
921
+ delete env.WAYLAND_DISPLAY;
922
+ delete env.XAUTHORITY;
533
923
  const child = node_child_process.spawn(process.execPath, [probePath], {
534
- env: {
535
- ...process.env,
536
- DISPLAY: xvfb.display,
537
- GDK_BACKEND: "x11"
538
- },
924
+ env,
539
925
  stdio: ["ignore", "pipe", "pipe"]
540
926
  });
541
- const timeout = setTimeout(() => {
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(() => {
542
950
  child.kill("SIGKILL");
543
- rejectProbe(
544
- errors.createGtkOperationFailedError("Timed out probing Xvfb pool.")
545
- );
546
- }, xvfbPoolProbeTimeoutMs);
951
+ rejectOnce(createXvfbProbeError("Timed out probing Xvfb pool.", false));
952
+ }, timeoutMs);
547
953
  child.stdout.on("data", (chunk) => {
548
954
  appendOutput$1(stdout, chunk);
549
955
  });
@@ -551,42 +957,71 @@ const runXvfbProbe = (xvfb) => new Promise((resolveProbe, rejectProbe) => {
551
957
  appendOutput$1(stderr, chunk);
552
958
  });
553
959
  child.once("error", (error) => {
554
- clearTimeout(timeout);
555
- rejectProbe(error);
960
+ rejectOnce(error);
556
961
  });
557
962
  child.once("exit", (code, signal) => {
558
- clearTimeout(timeout);
559
963
  if (code !== 0) {
560
- rejectProbe(
561
- errors.createGtkOperationFailedError(
964
+ const stderrText = stderr.join("");
965
+ rejectOnce(
966
+ createXvfbProbeError(
562
967
  `Xvfb pool probe failed: code=${String(code)}, signal=${String(
563
968
  signal
564
- )}` + formatOutputTail(stdout, stderr)
969
+ )}` + formatOutputTail(stdout, stderr),
970
+ isRetryableXvfbProbeExit(stderrText)
565
971
  )
566
972
  );
567
973
  return;
568
974
  }
569
975
  const output = stdout.join("").trim().split("\n").at(-1);
570
976
  if (output === void 0 || output.length === 0) {
571
- rejectProbe(
572
- errors.createGtkOperationFailedError(
573
- "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
574
981
  )
575
982
  );
576
983
  return;
577
984
  }
578
985
  try {
579
- resolveProbe(JSON.parse(output));
986
+ resolveOnce(JSON.parse(output));
580
987
  } catch (error) {
581
988
  const message = error instanceof Error ? error.message : String(error);
582
- rejectProbe(
583
- errors.createGtkOperationFailedError(
584
- `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
585
993
  )
586
994
  );
587
995
  }
588
996
  });
589
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
+ };
590
1025
  const cleanCheckXvfb = async (xvfb, allowedMappedWindowCount) => {
591
1026
  try {
592
1027
  const probe = await runXvfbProbe(xvfb);
@@ -605,7 +1040,7 @@ const returnXvfbToPool = async (xvfb, limits) => {
605
1040
  };
606
1041
  const allPoolKey = (xvfb) => `${xvfb.screen}
607
1042
  ${xvfb.trayHost ? "tray" : "no-tray"}`;
608
- const spawnDriverProcess = (driverPath, socketPath, effective, xvfb) => {
1043
+ const spawnDriverProcess = (driverPath, socketPath, effective, xvfb, systemOutputSink) => {
609
1044
  const driverArgs = [
610
1045
  "--socket",
611
1046
  socketPath,
@@ -638,14 +1073,46 @@ const spawnDriverProcess = (driverPath, socketPath, effective, xvfb) => {
638
1073
  env,
639
1074
  stdio: ["ignore", "pipe", "pipe"]
640
1075
  });
1076
+ const processState = {
1077
+ child,
1078
+ commandLine: [command.bin, ...command.args].join(" "),
1079
+ stderr,
1080
+ stdout,
1081
+ systemOutputSink
1082
+ };
641
1083
  child.stdout.on("data", (chunk) => {
642
1084
  appendOutput$1(stdout, chunk);
1085
+ appendSystemOutput(
1086
+ processState.systemOutputSink,
1087
+ "launcher-driver",
1088
+ "stdout",
1089
+ chunk
1090
+ );
643
1091
  });
644
1092
  child.stderr.on("data", (chunk) => {
645
1093
  appendOutput$1(stderr, chunk);
1094
+ appendSystemOutput(
1095
+ processState.systemOutputSink,
1096
+ "launcher-driver",
1097
+ "stderr",
1098
+ chunk
1099
+ );
646
1100
  });
647
- const commandLine = [command.bin, ...command.args].join(" ");
648
- return { child, commandLine, stderr, stdout };
1101
+ child.stdout.once("end", () => {
1102
+ flushSystemOutput(
1103
+ processState.systemOutputSink,
1104
+ "launcher-driver",
1105
+ "stdout"
1106
+ );
1107
+ });
1108
+ child.stderr.once("end", () => {
1109
+ flushSystemOutput(
1110
+ processState.systemOutputSink,
1111
+ "launcher-driver",
1112
+ "stderr"
1113
+ );
1114
+ });
1115
+ return processState;
649
1116
  };
650
1117
  const listenOnSocket = (server, socketPath) => new Promise((resolveListen, rejectListen) => {
651
1118
  const rejectFromError = (error) => {
@@ -724,7 +1191,13 @@ const waitForDriverReady = (server, processState) => new Promise((resolveReady,
724
1191
  const line = input.slice(0, newlineIndex);
725
1192
  input = input.slice(newlineIndex + 1);
726
1193
  try {
727
- if (parseReadyMessage(line) !== void 0) {
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) {
728
1201
  if (settled) {
729
1202
  return;
730
1203
  }
@@ -802,6 +1275,7 @@ const decodeCapture = (capture) => ({
802
1275
  visibleBounds: capture.visibleBounds
803
1276
  });
804
1277
  const createDriverSession = (socket, bufferedInput, processState, tempDirectory, poolOptions) => {
1278
+ const eventHandlers = /* @__PURE__ */ new Map();
805
1279
  const pending = /* @__PURE__ */ new Map();
806
1280
  let input = bufferedInput;
807
1281
  let nextRequestId = 1;
@@ -821,8 +1295,34 @@ const createDriverSession = (socket, bufferedInput, processState, tempDirectory,
821
1295
  closed = true;
822
1296
  rejectPending(errors.createGtkAppExitedError("Launcher driver has exited."));
823
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
+ };
824
1319
  const handleResponse = (line) => {
825
- const response = JSON.parse(line);
1320
+ const message = JSON.parse(line);
1321
+ if (isDriverEventMessage(message)) {
1322
+ handleEvent(message);
1323
+ return;
1324
+ }
1325
+ const response = message;
826
1326
  const entry = pending.get(response.id);
827
1327
  if (entry === void 0) {
828
1328
  return;
@@ -897,6 +1397,28 @@ const createDriverSession = (socket, bufferedInput, processState, tempDirectory,
897
1397
  });
898
1398
  });
899
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
+ };
900
1422
  const waitForExit = async () => {
901
1423
  const startedAt = Date.now();
902
1424
  while (processState.child.exitCode === null && processState.child.signalCode === null) {
@@ -981,10 +1503,10 @@ const createDriverSession = (socket, bufferedInput, processState, tempDirectory,
981
1503
  poolOptions.limits
982
1504
  );
983
1505
  };
984
- session = { release, request, terminate };
1506
+ session = { release, request, setSystemOutputSink, subscribe, terminate };
985
1507
  return session;
986
1508
  };
987
- const startFreshDriverSession = async (effective, xvfb, poolOptions) => {
1509
+ const startFreshDriverSession = async (effective, xvfb, poolOptions, systemOutputSink) => {
988
1510
  const driverPath = resolveDriverPath();
989
1511
  const tempDirectory = node_fs.mkdtempSync(
990
1512
  node_path.join(node_os.tmpdir(), `gestament-launcher-${process.pid}-${socketCounter}-`)
@@ -996,7 +1518,13 @@ const startFreshDriverSession = async (effective, xvfb, poolOptions) => {
996
1518
  try {
997
1519
  await listenOnSocket(server, socketPath);
998
1520
  server.unref();
999
- processState = spawnDriverProcess(driverPath, socketPath, effective, xvfb);
1521
+ processState = spawnDriverProcess(
1522
+ driverPath,
1523
+ socketPath,
1524
+ effective,
1525
+ xvfb,
1526
+ systemOutputSink
1527
+ );
1000
1528
  const connection = await waitForDriverReady(server, processState);
1001
1529
  server.close();
1002
1530
  const resolvedPoolOptions = poolOptions.mode === "all" && xvfb !== void 0 ? {
@@ -1022,7 +1550,7 @@ const startFreshDriverSession = async (effective, xvfb, poolOptions) => {
1022
1550
  throw error;
1023
1551
  }
1024
1552
  };
1025
- const startDriverSession = async (options) => {
1553
+ const startDriverSession = async (options, systemOutputSink) => {
1026
1554
  const display = resolveDisplay(options.display);
1027
1555
  const xvfbOptions = resolveXvfbOptions(options);
1028
1556
  const effective = resolveEffectiveDisplay(display, xvfbOptions);
@@ -1030,7 +1558,8 @@ const startDriverSession = async (options) => {
1030
1558
  return startFreshDriverSession(
1031
1559
  effective,
1032
1560
  void 0,
1033
- emptyDriverSessionPoolOptions()
1561
+ emptyDriverSessionPoolOptions(),
1562
+ systemOutputSink
1034
1563
  );
1035
1564
  }
1036
1565
  const pool = effective.xvfb.pool;
@@ -1038,18 +1567,24 @@ const startDriverSession = async (options) => {
1038
1567
  return startFreshDriverSession(
1039
1568
  effective,
1040
1569
  void 0,
1041
- emptyDriverSessionPoolOptions()
1570
+ emptyDriverSessionPoolOptions(),
1571
+ systemOutputSink
1042
1572
  );
1043
1573
  }
1044
1574
  if (pool.type === "xvfb") {
1045
- const xvfb2 = await leaseXvfb(effective.xvfb.screen);
1046
- return startFreshDriverSession(effective, xvfb2, {
1047
- allKey: void 0,
1048
- allowedMappedWindowCount: 0,
1049
- limits: poolLimits(pool),
1050
- mode: "xvfb",
1051
- xvfb: xvfb2
1052
- });
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
+ );
1053
1588
  }
1054
1589
  const key = allPoolKey(effective.xvfb);
1055
1590
  for (; ; ) {
@@ -1060,20 +1595,27 @@ const startDriverSession = async (options) => {
1060
1595
  if (idle.xvfb.child.exitCode === null && idle.xvfb.child.signalCode === null) {
1061
1596
  idle.lastUsedAt = Date.now();
1062
1597
  idle.xvfb.lastUsedAt = idle.lastUsedAt;
1598
+ idle.xvfb.systemOutputSink = systemOutputSink;
1599
+ idle.session.setSystemOutputSink(systemOutputSink);
1063
1600
  return idle.session;
1064
1601
  }
1065
1602
  await idle.session.terminate().catch(() => void 0);
1066
1603
  }
1067
- const xvfb = await leaseXvfb(effective.xvfb.screen);
1068
- return startFreshDriverSession(effective, xvfb, {
1069
- allKey: key,
1070
- allowedMappedWindowCount: 0,
1071
- limits: poolLimits(pool),
1072
- mode: "all",
1073
- xvfb
1074
- });
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
+ );
1075
1617
  };
1076
- const createLaunchPayload = (options, args) => {
1618
+ const createLaunchPayload = (options, args, launchOptions, outputScopeId) => {
1077
1619
  const display = resolveDisplay(options.display);
1078
1620
  const xvfb = resolveXvfbOptions(options);
1079
1621
  const effective = resolveEffectiveDisplay(display, xvfb);
@@ -1081,12 +1623,25 @@ const createLaunchPayload = (options, args) => {
1081
1623
  appPath: options.appPath,
1082
1624
  args: [...options.args ?? [], ...args],
1083
1625
  env: resolveLauncherEnvironment(options, effective),
1626
+ outputBufferBytes: resolveOutputBufferBytes(
1627
+ options.outputBufferBytes,
1628
+ launchOptions?.outputBufferBytes
1629
+ ),
1630
+ outputScopeId,
1084
1631
  timeoutMs: options.timeoutMs ?? null
1085
1632
  };
1086
1633
  };
1634
+ const createEnvironmentPayload = (options) => {
1635
+ const display = resolveDisplay(options.display);
1636
+ const xvfb = resolveXvfbOptions(options);
1637
+ const effective = resolveEffectiveDisplay(display, xvfb);
1638
+ return {
1639
+ env: resolveLauncherEnvironment(options, effective)
1640
+ };
1641
+ };
1087
1642
  const elementRefToProxy = (session, ref) => ref === null ? void 0 : createProxyGtkElement(session, ref);
1088
1643
  const trayRefToProxy = (session, ref) => ref === null ? void 0 : createProxyGtkTrayItem(session, ref);
1089
- const createProxyGtkApp = (session, ref) => {
1644
+ const createProxyGtkApp = (session, ref, outputSubscription) => {
1090
1645
  let released = false;
1091
1646
  const assertNotReleased = () => {
1092
1647
  if (released) {
@@ -1105,10 +1660,15 @@ const createProxyGtkApp = (session, ref) => {
1105
1660
  return;
1106
1661
  }
1107
1662
  released = true;
1663
+ outputSubscription?.dispose();
1108
1664
  await session.request("app.release", { appId: ref.appId });
1109
1665
  };
1110
1666
  const app = {
1111
1667
  capture: async () => decodeCapture(await appRequest("app.capture")),
1668
+ environment: async () => wireEnvironmentToGtkAppEnvironment(
1669
+ await appRequest("app.environment")
1670
+ ),
1671
+ output: () => appRequest("app.output"),
1112
1672
  findById: async (id) => elementRefToProxy(
1113
1673
  session,
1114
1674
  await appRequest("app.findById", { id })
@@ -1221,6 +1781,13 @@ const createProxyGtkElement = (session, ref) => {
1221
1781
  };
1222
1782
  switch (ref.kind) {
1223
1783
  case "window":
1784
+ target.bounds = () => session.request("element.bounds", { elementId });
1785
+ target.resizeHints = () => session.request("window.resizeHints", {
1786
+ elementId
1787
+ });
1788
+ target.x11Info = () => session.request("window.x11Info", { elementId });
1789
+ addChildContainerProxyOperations(session, elementId, target);
1790
+ break;
1224
1791
  case "container":
1225
1792
  case "menu":
1226
1793
  addChildContainerProxyOperations(session, elementId, target);
@@ -1329,19 +1896,84 @@ const createProxyGtkTrayItem = (session, ref) => {
1329
1896
  };
1330
1897
  };
1331
1898
  const createDriverBackedGtkAppLauncher = (options) => {
1899
+ const outputSubscriptions = /* @__PURE__ */ new Set();
1900
+ let systemOutputRecorder = createGtkSystemOutputRecorder(
1901
+ normalizeOutputBufferBytes(
1902
+ options.systemOutputBufferBytes,
1903
+ "systemOutputBufferBytes"
1904
+ )
1905
+ );
1332
1906
  let sessionPromise;
1333
1907
  const ensureSession = () => {
1334
- sessionPromise ??= startDriverSession(options);
1908
+ if (sessionPromise === void 0) {
1909
+ systemOutputRecorder = createGtkSystemOutputRecorder(
1910
+ normalizeOutputBufferBytes(
1911
+ options.systemOutputBufferBytes,
1912
+ "systemOutputBufferBytes"
1913
+ )
1914
+ );
1915
+ sessionPromise = startDriverSession(
1916
+ options,
1917
+ createSystemOutputSink(systemOutputRecorder, options.onSystemOutput)
1918
+ );
1919
+ }
1335
1920
  return sessionPromise;
1336
1921
  };
1337
- const launch = async (args) => {
1922
+ const trackOutputSubscription = (subscription) => {
1923
+ let tracked;
1924
+ tracked = {
1925
+ dispose: () => {
1926
+ if (!outputSubscriptions.delete(tracked)) {
1927
+ return;
1928
+ }
1929
+ subscription.dispose();
1930
+ }
1931
+ };
1932
+ outputSubscriptions.add(tracked);
1933
+ return tracked;
1934
+ };
1935
+ const disposeOutputSubscriptions = () => {
1936
+ for (const subscription of [...outputSubscriptions]) {
1937
+ subscription.dispose();
1938
+ }
1939
+ };
1940
+ const launch = async (args, launchOptions) => {
1338
1941
  const session = await ensureSession();
1339
- const appRef = await session.request(
1340
- "launcher.launch",
1341
- createLaunchPayload(options, args ?? [])
1942
+ const onOutput = launchOptions?.onOutput;
1943
+ const outputScopeId = onOutput === void 0 ? null : createOutputScopeId();
1944
+ const outputSubscription = outputScopeId === null ? void 0 : trackOutputSubscription(
1945
+ session.subscribe("app.output", outputScopeId, (value) => {
1946
+ onOutput?.(value);
1947
+ })
1948
+ );
1949
+ const payload = createLaunchPayload(
1950
+ options,
1951
+ args ?? [],
1952
+ launchOptions,
1953
+ outputScopeId
1342
1954
  );
1343
- return createProxyGtkApp(session, appRef);
1955
+ try {
1956
+ const appRef = await session.request(
1957
+ "launcher.launch",
1958
+ payload
1959
+ );
1960
+ return createProxyGtkApp(session, appRef, outputSubscription);
1961
+ } catch (error) {
1962
+ outputSubscription?.dispose();
1963
+ throw error;
1964
+ }
1344
1965
  };
1966
+ const environment = async () => {
1967
+ const payload = createEnvironmentPayload(options);
1968
+ const session = await ensureSession();
1969
+ return wireEnvironmentToGtkAppEnvironment(
1970
+ await session.request(
1971
+ "launcher.environment",
1972
+ payload
1973
+ )
1974
+ );
1975
+ };
1976
+ const systemOutput = () => Promise.resolve(systemOutputRecorder.snapshot());
1345
1977
  const release = async () => {
1346
1978
  const releasingSession = sessionPromise;
1347
1979
  sessionPromise = void 0;
@@ -1349,11 +1981,17 @@ const createDriverBackedGtkAppLauncher = (options) => {
1349
1981
  return;
1350
1982
  }
1351
1983
  const session = await releasingSession;
1352
- await session.release();
1984
+ try {
1985
+ await session.release();
1986
+ } finally {
1987
+ disposeOutputSubscriptions();
1988
+ }
1353
1989
  };
1354
1990
  return {
1991
+ environment,
1355
1992
  launch,
1356
1993
  release,
1994
+ systemOutput,
1357
1995
  [Symbol.asyncDispose]: release
1358
1996
  };
1359
1997
  };
@@ -1760,6 +2398,9 @@ const createImageInfoOperation = (handle) => async () => {
1760
2398
  capture: async () => native.nativeCaptureBounds(info.bounds)
1761
2399
  };
1762
2400
  };
2401
+ const createBoundsOperation = (handle) => async () => native.nativeBounds(handle);
2402
+ const createResizeHintsOperation = (handle) => async () => native.nativeResizeHints(handle);
2403
+ const createX11InfoOperation = (handle) => async () => native.nativeX11Info(handle);
1763
2404
  const createValueOperation = (handle) => async () => native.nativeValueInfo(handle).value;
1764
2405
  const createSetValueOperation = (handle) => async (value) => {
1765
2406
  assertFiniteNumber("value", value);
@@ -1931,7 +2572,10 @@ const createGtkElement = (handle) => {
1931
2572
  return {
1932
2573
  ...common,
1933
2574
  kind: "window",
1934
- ...createChildContainerOperations(handle, void 0)
2575
+ bounds: createBoundsOperation(handle),
2576
+ ...createChildContainerOperations(handle, void 0),
2577
+ resizeHints: createResizeHintsOperation(handle),
2578
+ x11Info: createX11InfoOperation(handle)
1935
2579
  };
1936
2580
  case "button":
1937
2581
  return { ...common, kind: "button", click: createClickOperation(handle) };
@@ -2259,30 +2903,70 @@ const createGtkAppEnvironment = (baseEnv, overrides) => {
2259
2903
  const launchGtkApp = (appPath, args, options) => {
2260
2904
  const _args = args ?? [];
2261
2905
  const _timeoutMs = options?.timeoutMs ?? 1e4;
2906
+ const outputBufferBytes = normalizeOutputBufferBytes(
2907
+ options?.outputBufferBytes
2908
+ );
2909
+ const outputRecorder = createGtkAppOutputRecorder(outputBufferBytes);
2910
+ const appEnvironment = createGtkAppEnvironment(process.env, options?.env);
2262
2911
  const child = node_child_process.spawn(appPath, [..._args], {
2263
- env: createGtkAppEnvironment(process.env, options?.env),
2912
+ env: appEnvironment,
2264
2913
  stdio: "pipe"
2265
2914
  });
2915
+ let resolveClosed;
2916
+ const closedPromise = new Promise((resolve) => {
2917
+ resolveClosed = resolve;
2918
+ });
2266
2919
  const state = {
2267
2920
  atspiReadiness: "missing-bus-name",
2268
2921
  atspiReady: false,
2922
+ closed: false,
2923
+ closedPromise,
2269
2924
  exitCode: null,
2270
2925
  exitSignal: null,
2271
2926
  process: child,
2927
+ released: false,
2272
2928
  stderr: [],
2273
2929
  stdout: []
2274
2930
  };
2931
+ const notifyOutput = (stream, chunk) => {
2932
+ notifyGtkAppOutput(options?.onOutput, outputRecorder.append(stream, chunk));
2933
+ };
2934
+ const flushOutput = (stream) => {
2935
+ notifyGtkAppOutput(options?.onOutput, outputRecorder.flush(stream));
2936
+ };
2275
2937
  child.stdout.on("data", (chunk) => {
2276
2938
  appendOutput(state.stdout, chunk);
2939
+ notifyOutput("stdout", chunk);
2277
2940
  });
2278
2941
  child.stderr.on("data", (chunk) => {
2279
2942
  appendOutput(state.stderr, chunk);
2943
+ notifyOutput("stderr", chunk);
2944
+ });
2945
+ child.stdout.on("end", () => {
2946
+ flushOutput("stdout");
2947
+ });
2948
+ child.stderr.on("end", () => {
2949
+ flushOutput("stderr");
2280
2950
  });
2281
2951
  child.on("exit", (code, signal) => {
2282
2952
  state.exitCode = code;
2283
2953
  state.exitSignal = signal;
2284
2954
  });
2955
+ child.on("close", (code, signal) => {
2956
+ if (state.exitCode === null && state.exitSignal === null) {
2957
+ state.exitCode = code;
2958
+ state.exitSignal = signal;
2959
+ }
2960
+ flushOutput("stdout");
2961
+ flushOutput("stderr");
2962
+ state.closed = true;
2963
+ resolveClosed?.();
2964
+ });
2285
2965
  const release = async () => {
2966
+ if (state.released) {
2967
+ return;
2968
+ }
2969
+ state.released = true;
2286
2970
  if (state.exitCode !== null || state.exitSignal !== null) {
2287
2971
  return;
2288
2972
  }
@@ -2296,6 +2980,11 @@ const launchGtkApp = (appPath, args, options) => {
2296
2980
  await wait.delay(25);
2297
2981
  }
2298
2982
  };
2983
+ const assertNotReleased = () => {
2984
+ if (state.released) {
2985
+ throw errors.createGtkAppExitedError("GTK application has been released.");
2986
+ }
2987
+ };
2299
2988
  const findById = async (id) => {
2300
2989
  const startedAt = Date.now();
2301
2990
  const timeoutMs = wait.effectiveWaitTimeoutMs(_timeoutMs);
@@ -2403,6 +3092,7 @@ const launchGtkApp = (appPath, args, options) => {
2403
3092
  };
2404
3093
  const app = {
2405
3094
  capture: async () => {
3095
+ assertNotReleased();
2406
3096
  assertProcessRunning(state, appPath);
2407
3097
  try {
2408
3098
  return native.nativeCaptureScreen();
@@ -2410,6 +3100,21 @@ const launchGtkApp = (appPath, args, options) => {
2410
3100
  throw errors.normalizeNativeError(error);
2411
3101
  }
2412
3102
  },
3103
+ environment: async () => {
3104
+ assertNotReleased();
3105
+ assertProcessRunning(state, appPath);
3106
+ return { ...appEnvironment };
3107
+ },
3108
+ output: async () => {
3109
+ assertNotReleased();
3110
+ if ((state.exitCode !== null || state.exitSignal !== null) && !state.closed) {
3111
+ await state.closedPromise;
3112
+ }
3113
+ return outputRecorder.snapshot(
3114
+ state.exitCode,
3115
+ state.exitSignal === null ? null : state.exitSignal
3116
+ );
3117
+ },
2413
3118
  findById,
2414
3119
  findByPath,
2415
3120
  getById,
@@ -2476,4 +3181,4 @@ const createGtkAppLauncher = (options) => createDriverBackedGtkAppLauncher(optio
2476
3181
  exports.createGtkAppEnvironment = createGtkAppEnvironment;
2477
3182
  exports.createGtkAppLauncher = createGtkAppLauncher;
2478
3183
  exports.launchGtkApp = launchGtkApp;
2479
- //# sourceMappingURL=launchGtkApp-BIO_5Xjn.cjs.map
3184
+ //# sourceMappingURL=launchGtkApp-CzYcrc9f.cjs.map