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.
Files changed (73) hide show
  1. package/README.md +20 -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 +103 -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 +103 -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.d.ts +2 -2
  24. package/dist/gestament.cjs +279 -0
  25. package/dist/gestament.cjs.map +1 -0
  26. package/dist/gestament.d.ts +29 -0
  27. package/dist/gestament.d.ts.map +1 -0
  28. package/dist/gestament.mjs +278 -0
  29. package/dist/gestament.mjs.map +1 -0
  30. package/dist/index.cjs +1 -1
  31. package/dist/index.d.ts +2 -2
  32. package/dist/index.mjs +1 -1
  33. package/dist/{launchGtkApp-Bst1BFbD.js → launchGtkApp-B3FMzhrc.js} +741 -74
  34. package/dist/launchGtkApp-B3FMzhrc.js.map +1 -0
  35. package/dist/{launchGtkApp-BfELuV-H.cjs → launchGtkApp-OggUtTTC.cjs} +743 -76
  36. package/dist/launchGtkApp-OggUtTTC.cjs.map +1 -0
  37. package/dist/launchGtkApp.d.ts +2 -2
  38. package/dist/launchGtkApp.d.ts.map +1 -1
  39. package/dist/launcherDriverProtocol.d.ts +37 -4
  40. package/dist/launcherDriverProtocol.d.ts.map +1 -1
  41. package/dist/{native-C6MsIBNF.js → native-C2tzSvVW.js} +25 -11
  42. package/dist/native-C2tzSvVW.js.map +1 -0
  43. package/dist/{native-BUWDWMBB.cjs → native-Xz0gjKti.cjs} +20 -6
  44. package/dist/native-Xz0gjKti.cjs.map +1 -0
  45. package/dist/native.d.ts +8 -2
  46. package/dist/native.d.ts.map +1 -1
  47. package/dist/output.d.ts +27 -0
  48. package/dist/output.d.ts.map +1 -0
  49. package/dist/packageMetadata-BQE2KNaa.js +5 -0
  50. package/dist/packageMetadata-BQE2KNaa.js.map +1 -0
  51. package/dist/packageMetadata-BqAHZ5WO.cjs +4 -0
  52. package/dist/packageMetadata-BqAHZ5WO.cjs.map +1 -0
  53. package/dist/prerequisites.d.ts +2 -2
  54. package/dist/testing.d.ts +2 -2
  55. package/dist/tray.d.ts +2 -2
  56. package/dist/types.d.ts +206 -3
  57. package/dist/types.d.ts.map +1 -1
  58. package/dist/wait.d.ts +2 -2
  59. package/package.json +8 -7
  60. package/prebuilds/linux-arm/gtk3/node.napi.armv7.glibc.node +0 -0
  61. package/prebuilds/linux-arm/gtk4/node.napi.armv7.glibc.node +0 -0
  62. package/prebuilds/linux-arm64/gtk3/node.napi.glibc.node +0 -0
  63. package/prebuilds/linux-arm64/gtk4/node.napi.glibc.node +0 -0
  64. package/prebuilds/linux-ia32/gtk3/node.napi.glibc.node +0 -0
  65. package/prebuilds/linux-ia32/gtk4/node.napi.glibc.node +0 -0
  66. package/prebuilds/linux-riscv64/gtk3/node.napi.glibc.node +0 -0
  67. package/prebuilds/linux-riscv64/gtk4/node.napi.glibc.node +0 -0
  68. package/prebuilds/linux-x64/gtk3/node.napi.glibc.node +0 -0
  69. package/prebuilds/linux-x64/gtk4/node.napi.glibc.node +0 -0
  70. package/dist/launchGtkApp-BfELuV-H.cjs.map +0 -1
  71. package/dist/launchGtkApp-Bst1BFbD.js.map +0 -1
  72. package/dist/native-BUWDWMBB.cjs.map +0 -1
  73. 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-BUWDWMBB.cjs");
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-BfELuV-H.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-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-BfELuV-H.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-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 runXvfbProbe = (xvfb) => new Promise((resolveProbe, rejectProbe) => {
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
- 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(() => {
589
950
  child.kill("SIGKILL");
590
- rejectProbe(
591
- errors.createGtkOperationFailedError("Timed out probing Xvfb pool.")
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
- clearTimeout(timeout);
602
- rejectProbe(error);
960
+ rejectOnce(error);
603
961
  });
604
962
  child.once("exit", (code, signal) => {
605
- clearTimeout(timeout);
606
963
  if (code !== 0) {
607
- rejectProbe(
608
- errors.createGtkOperationFailedError(
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
- rejectProbe(
619
- errors.createGtkOperationFailedError(
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
- resolveProbe(JSON.parse(output));
986
+ resolveOnce(JSON.parse(output));
627
987
  } catch (error) {
628
988
  const message = error instanceof Error ? error.message : String(error);
629
- rejectProbe(
630
- errors.createGtkOperationFailedError(
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
- const commandLine = [command.bin, ...command.args].join(" ");
695
- return { child, commandLine, stderr, stdout };
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
- 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) {
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 response = JSON.parse(line);
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(driverPath, socketPath, effective, xvfb);
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(effective, xvfb2, {
1094
- allKey: void 0,
1095
- allowedMappedWindowCount: 0,
1096
- limits: poolLimits(pool),
1097
- mode: "xvfb",
1098
- xvfb: xvfb2
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(effective, xvfb, {
1116
- allKey: key,
1117
- allowedMappedWindowCount: 0,
1118
- limits: poolLimits(pool),
1119
- mode: "all",
1120
- xvfb
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 ??= startDriverSession(options);
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 launch = async (args) => {
1403
- const payload = createLaunchPayload(options, args ?? []);
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 appRef = await session.request(
1406
- "launcher.launch",
1407
- payload
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
- return createProxyGtkApp(session, appRef);
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
- await session.release();
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-BfELuV-H.cjs.map
3234
+ //# sourceMappingURL=launchGtkApp-OggUtTTC.cjs.map