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