gestament 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/README.md +22 -5
  2. package/dist/displaySession.d.ts +2 -2
  3. package/dist/displaySession.d.ts.map +1 -1
  4. package/dist/element.d.ts +2 -2
  5. package/dist/element.d.ts.map +1 -1
  6. package/dist/errors.d.ts +2 -2
  7. package/dist/generated/packageMetadata.d.ts +4 -4
  8. package/dist/gestament-config.d.ts +2 -2
  9. package/dist/gestament-launcher-driver.cjs +118 -5
  10. package/dist/gestament-launcher-driver.cjs.map +1 -1
  11. package/dist/gestament-launcher-driver.d.ts +2 -2
  12. package/dist/gestament-launcher-driver.mjs +118 -5
  13. package/dist/gestament-launcher-driver.mjs.map +1 -1
  14. package/dist/gestament-tray-host.cjs +9 -1
  15. package/dist/gestament-tray-host.cjs.map +1 -1
  16. package/dist/gestament-tray-host.d.ts +2 -2
  17. package/dist/gestament-tray-host.mjs +9 -1
  18. package/dist/gestament-tray-host.mjs.map +1 -1
  19. package/dist/gestament-xvfb-pool-probe.cjs +1 -1
  20. package/dist/gestament-xvfb-pool-probe.d.ts +2 -2
  21. package/dist/gestament-xvfb-pool-probe.mjs +1 -1
  22. package/dist/gestament-xvfb-worker.d.ts +2 -2
  23. package/dist/gestament-xvfb.cjs +7 -2
  24. package/dist/gestament-xvfb.cjs.map +1 -1
  25. package/dist/gestament-xvfb.d.ts +2 -2
  26. package/dist/gestament-xvfb.mjs +7 -2
  27. package/dist/gestament-xvfb.mjs.map +1 -1
  28. package/dist/gestament.cjs +279 -0
  29. package/dist/gestament.cjs.map +1 -0
  30. package/dist/gestament.d.ts +29 -0
  31. package/dist/gestament.d.ts.map +1 -0
  32. package/dist/gestament.mjs +278 -0
  33. package/dist/gestament.mjs.map +1 -0
  34. package/dist/index.cjs +1 -1
  35. package/dist/index.d.ts +2 -2
  36. package/dist/index.mjs +1 -1
  37. package/dist/{launchGtkApp-BIO_5Xjn.cjs → launchGtkApp-CzYcrc9f.cjs} +794 -89
  38. package/dist/launchGtkApp-CzYcrc9f.cjs.map +1 -0
  39. package/dist/{launchGtkApp-qi1qm5G4.js → launchGtkApp-EI6OIpPI.js} +792 -87
  40. package/dist/launchGtkApp-EI6OIpPI.js.map +1 -0
  41. package/dist/launchGtkApp.d.ts +2 -2
  42. package/dist/launchGtkApp.d.ts.map +1 -1
  43. package/dist/launcherDriverProtocol.d.ts +29 -4
  44. package/dist/launcherDriverProtocol.d.ts.map +1 -1
  45. package/dist/{native-CWdUmdty.js → native-DlCBBWlV.js} +16 -10
  46. package/dist/native-DlCBBWlV.js.map +1 -0
  47. package/dist/{native-CBXaFWP-.cjs → native-Kfm95Uxw.cjs} +12 -6
  48. package/dist/native-Kfm95Uxw.cjs.map +1 -0
  49. package/dist/native.d.ts +23 -2
  50. package/dist/native.d.ts.map +1 -1
  51. package/dist/output.d.ts +27 -0
  52. package/dist/output.d.ts.map +1 -0
  53. package/dist/packageMetadata-CewXH0iF.cjs +4 -0
  54. package/dist/packageMetadata-CewXH0iF.cjs.map +1 -0
  55. package/dist/packageMetadata-DjxyJCJm.js +5 -0
  56. package/dist/packageMetadata-DjxyJCJm.js.map +1 -0
  57. package/dist/prerequisites.d.ts +2 -2
  58. package/dist/testing.d.ts +2 -2
  59. package/dist/tray.d.ts +2 -2
  60. package/dist/types.d.ts +277 -3
  61. package/dist/types.d.ts.map +1 -1
  62. package/dist/wait.d.ts +2 -2
  63. package/package.json +8 -7
  64. package/prebuilds/linux-arm/gtk3/node.napi.armv7.glibc.node +0 -0
  65. package/prebuilds/linux-arm/gtk4/node.napi.armv7.glibc.node +0 -0
  66. package/prebuilds/linux-arm64/gtk3/node.napi.glibc.node +0 -0
  67. package/prebuilds/linux-arm64/gtk4/node.napi.glibc.node +0 -0
  68. package/prebuilds/linux-ia32/gtk3/node.napi.glibc.node +0 -0
  69. package/prebuilds/linux-ia32/gtk4/node.napi.glibc.node +0 -0
  70. package/prebuilds/linux-riscv64/gtk3/node.napi.glibc.node +0 -0
  71. package/prebuilds/linux-riscv64/gtk4/node.napi.glibc.node +0 -0
  72. package/prebuilds/linux-x64/gtk3/node.napi.glibc.node +0 -0
  73. package/prebuilds/linux-x64/gtk4/node.napi.glibc.node +0 -0
  74. package/dist/launchGtkApp-BIO_5Xjn.cjs.map +0 -1
  75. package/dist/launchGtkApp-qi1qm5G4.js.map +0 -1
  76. package/dist/native-CBXaFWP-.cjs.map +0 -1
  77. package/dist/native-CWdUmdty.js.map +0 -1
@@ -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 nativeCapture, G as nativeTableCellAt, H as nativeFindAnyById, I as nativeTrayItems, J as nativeWindowCount, K as nativeWindowAt, a as nativeCaptureScreen, L as nativeFindById, M as nativeProcessAtspiReadiness } from "./native-CWdUmdty.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-DlCBBWlV.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,8 +245,23 @@ 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;
253
+ const sessionOwnedEnvironmentKeys = [
254
+ "DISPLAY",
255
+ "WAYLAND_DISPLAY",
256
+ "GDK_BACKEND",
257
+ "DBUS_SESSION_BUS_ADDRESS",
258
+ "AT_SPI_BUS_ADDRESS",
259
+ "NO_AT_BRIDGE",
260
+ "XAUTHORITY",
261
+ "GESTAMENT_XVFB_ACTIVE",
262
+ "XDG_SESSION_TYPE"
263
+ ];
264
+ let outputScopeCounter = 0;
25
265
  let socketCounter = 0;
26
266
  const leasedDisplayNumbers = /* @__PURE__ */ new Set();
27
267
  const idleXvfbByKey = /* @__PURE__ */ new Map();
@@ -38,6 +278,12 @@ const appendOutput$1 = (lines, chunk) => {
38
278
  lines.splice(0, lines.length - 40);
39
279
  }
40
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
+ };
41
287
  const unrefHandle = (handle) => {
42
288
  const maybeRefHandle = handle;
43
289
  if (typeof maybeRefHandle.unref === "function") {
@@ -56,6 +302,41 @@ ${stdoutText}
56
302
  stderr:
57
303
  ${stderrText}`;
58
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;
59
340
  const getHostDisplayState = () => ({
60
341
  display: process.env.DISPLAY,
61
342
  waylandDisplay: process.env.WAYLAND_DISPLAY
@@ -81,6 +362,63 @@ const resolveDisplay = (display) => {
81
362
  );
82
363
  };
83
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
+ };
84
422
  const resolvePoolLimit = (name, value, defaultValue) => {
85
423
  if (value === void 0) {
86
424
  return defaultValue;
@@ -228,6 +566,7 @@ const retainIdleXvfb = async (xvfb, limits) => {
228
566
  await terminateXvfb(xvfb);
229
567
  return;
230
568
  }
569
+ xvfb.systemOutputSink = void 0;
231
570
  xvfb.lastUsedAt = Date.now();
232
571
  pushArrayEntry(idleXvfbByKey, xvfb.key, xvfb);
233
572
  await trimIdleXvfbKey(xvfb.key, limits.maxIdlePerKey);
@@ -238,6 +577,8 @@ const retainIdleAllSession = async (session, limits) => {
238
577
  await session.session.terminate();
239
578
  return;
240
579
  }
580
+ session.session.setSystemOutputSink(void 0);
581
+ session.xvfb.systemOutputSink = void 0;
241
582
  session.lastUsedAt = Date.now();
242
583
  session.xvfb.lastUsedAt = session.lastUsedAt;
243
584
  pushArrayEntry(idleAllByKey, session.key, session);
@@ -311,12 +652,34 @@ const toWireEnvironment = (env) => {
311
652
  }
312
653
  return wireEnv;
313
654
  };
314
- const resolveLauncherEnvironment = (options, effective) => toWireEnvironment({
315
- GDK_BACKEND: resolveGdkBackend(effective),
316
- GSETTINGS_BACKEND: options.gsettings === null ? void 0 : options.gsettings ?? defaultGSettings,
317
- GTK_THEME: options.theme === null ? void 0 : options.theme ?? defaultTheme,
318
- ...options.env
319
- });
655
+ const wireEnvironmentToGtkAppEnvironment = (env) => {
656
+ const appEnv = {};
657
+ for (const [key, value] of Object.entries(env)) {
658
+ appEnv[key] = value === null ? void 0 : value;
659
+ }
660
+ return appEnv;
661
+ };
662
+ const assertNoSessionOwnedEnvironmentOverrides = (options, effective) => {
663
+ if (effective.kind !== "xvfb" || options.env === void 0) {
664
+ return;
665
+ }
666
+ for (const key of sessionOwnedEnvironmentKeys) {
667
+ if (Object.hasOwn(options.env, key)) {
668
+ throw createGtkInvalidArgumentError(
669
+ `options.env must not override ${key} when using internal Xvfb.`
670
+ );
671
+ }
672
+ }
673
+ };
674
+ const resolveLauncherEnvironment = (options, effective) => {
675
+ assertNoSessionOwnedEnvironmentOverrides(options, effective);
676
+ return toWireEnvironment({
677
+ GDK_BACKEND: resolveGdkBackend(effective),
678
+ GSETTINGS_BACKEND: options.gsettings === null ? void 0 : options.gsettings ?? defaultGSettings,
679
+ GTK_THEME: options.theme === null ? void 0 : options.theme ?? defaultTheme,
680
+ ...options.env
681
+ });
682
+ };
320
683
  const resolveDriverPath = () => {
321
684
  const driverPath = resolve(
322
685
  dirname(fileURLToPath(import.meta.url)),
@@ -350,11 +713,17 @@ const createDriverEnvironment = (effective, xvfb) => {
350
713
  delete env.AT_SPI_BUS_ADDRESS;
351
714
  delete env.NO_AT_BRIDGE;
352
715
  if (effective.kind === "xvfb") {
716
+ delete env.DBUS_SESSION_BUS_ADDRESS;
717
+ delete env.DISPLAY;
718
+ delete env.WAYLAND_DISPLAY;
719
+ delete env.AT_SPI_BUS_ADDRESS;
720
+ delete env.NO_AT_BRIDGE;
721
+ delete env.XAUTHORITY;
353
722
  env.GDK_BACKEND = "x11";
354
723
  env.GESTAMENT_XVFB_ACTIVE = "1";
724
+ env.XDG_SESSION_TYPE = "x11";
355
725
  if (xvfb !== void 0) {
356
726
  env.DISPLAY = xvfb.display;
357
- delete env.XAUTHORITY;
358
727
  }
359
728
  }
360
729
  return env;
@@ -422,7 +791,7 @@ const installPoolCleanup = () => {
422
791
  }
423
792
  });
424
793
  };
425
- const spawnDirectXvfb = async (screen) => {
794
+ const spawnDirectXvfb = async (screen, systemOutputSink) => {
426
795
  installPoolCleanup();
427
796
  for (let displayNumber = firstPooledDisplayNumber; displayNumber <= lastPooledDisplayNumber; displayNumber += 1) {
428
797
  if (!isDisplayNumberAvailable(displayNumber)) {
@@ -439,11 +808,30 @@ const spawnDirectXvfb = async (screen) => {
439
808
  stdio: ["ignore", "pipe", "pipe"]
440
809
  }
441
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
+ };
442
822
  child.stdout.on("data", (chunk) => {
443
823
  appendOutput$1(stdout, chunk);
824
+ appendSystemOutput(xvfb.systemOutputSink, "xvfb", "stdout", chunk);
444
825
  });
445
826
  child.stderr.on("data", (chunk) => {
446
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");
447
835
  });
448
836
  child.unref();
449
837
  unrefHandle(child.stdout);
@@ -466,22 +854,10 @@ const spawnDirectXvfb = async (screen) => {
466
854
  });
467
855
  })
468
856
  ]);
469
- const xvfb = {
470
- child,
471
- display: `:${displayNumber}`,
472
- displayNumber,
473
- key: screen,
474
- lastUsedAt: Date.now(),
475
- screen,
476
- stderr,
477
- stdout
478
- };
479
857
  directXvfbs.add(xvfb);
480
858
  return xvfb;
481
859
  } catch (error) {
482
- killXvfbNow({
483
- child
484
- });
860
+ killXvfbNow(xvfb);
485
861
  leasedDisplayNumbers.delete(displayNumber);
486
862
  const message = error instanceof Error ? error.message : String(error);
487
863
  if (message.includes("ENOENT")) {
@@ -509,39 +885,69 @@ const terminateXvfb = async (xvfb) => {
509
885
  await delay(25);
510
886
  }
511
887
  }
888
+ xvfb.systemOutputSink = void 0;
512
889
  leasedDisplayNumbers.delete(xvfb.displayNumber);
513
890
  };
514
- const leaseXvfb = async (screen) => {
891
+ const leaseXvfb = async (screen, systemOutputSink) => {
515
892
  for (; ; ) {
516
893
  const idle = popArrayEntry(idleXvfbByKey, screen);
517
894
  if (idle === void 0) {
518
- return spawnDirectXvfb(screen);
895
+ return spawnDirectXvfb(screen, systemOutputSink);
519
896
  }
520
897
  if (idle.child.exitCode === null && idle.child.signalCode === null) {
521
898
  idle.lastUsedAt = Date.now();
899
+ idle.systemOutputSink = systemOutputSink;
522
900
  return idle;
523
901
  }
524
902
  await terminateXvfb(idle);
525
903
  }
526
904
  };
527
- const runXvfbProbe = (xvfb) => new Promise((resolveProbe, rejectProbe) => {
905
+ const runXvfbProbeOnce = (xvfb, timeoutMs) => new Promise((resolveProbe, rejectProbe) => {
528
906
  const probePath = resolveXvfbPoolProbePath();
529
907
  const stdout = [];
530
908
  const stderr = [];
909
+ const env = {
910
+ ...process.env,
911
+ DISPLAY: xvfb.display,
912
+ GDK_BACKEND: "x11",
913
+ GESTAMENT_XVFB_ACTIVE: "1",
914
+ XDG_SESSION_TYPE: "x11"
915
+ };
916
+ delete env.AT_SPI_BUS_ADDRESS;
917
+ delete env.DBUS_SESSION_BUS_ADDRESS;
918
+ delete env.NO_AT_BRIDGE;
919
+ delete env.WAYLAND_DISPLAY;
920
+ delete env.XAUTHORITY;
531
921
  const child = spawn(process.execPath, [probePath], {
532
- env: {
533
- ...process.env,
534
- DISPLAY: xvfb.display,
535
- GDK_BACKEND: "x11"
536
- },
922
+ env,
537
923
  stdio: ["ignore", "pipe", "pipe"]
538
924
  });
539
- 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(() => {
540
948
  child.kill("SIGKILL");
541
- rejectProbe(
542
- createGtkOperationFailedError("Timed out probing Xvfb pool.")
543
- );
544
- }, xvfbPoolProbeTimeoutMs);
949
+ rejectOnce(createXvfbProbeError("Timed out probing Xvfb pool.", false));
950
+ }, timeoutMs);
545
951
  child.stdout.on("data", (chunk) => {
546
952
  appendOutput$1(stdout, chunk);
547
953
  });
@@ -549,42 +955,71 @@ const runXvfbProbe = (xvfb) => new Promise((resolveProbe, rejectProbe) => {
549
955
  appendOutput$1(stderr, chunk);
550
956
  });
551
957
  child.once("error", (error) => {
552
- clearTimeout(timeout);
553
- rejectProbe(error);
958
+ rejectOnce(error);
554
959
  });
555
960
  child.once("exit", (code, signal) => {
556
- clearTimeout(timeout);
557
961
  if (code !== 0) {
558
- rejectProbe(
559
- createGtkOperationFailedError(
962
+ const stderrText = stderr.join("");
963
+ rejectOnce(
964
+ createXvfbProbeError(
560
965
  `Xvfb pool probe failed: code=${String(code)}, signal=${String(
561
966
  signal
562
- )}` + formatOutputTail(stdout, stderr)
967
+ )}` + formatOutputTail(stdout, stderr),
968
+ isRetryableXvfbProbeExit(stderrText)
563
969
  )
564
970
  );
565
971
  return;
566
972
  }
567
973
  const output = stdout.join("").trim().split("\n").at(-1);
568
974
  if (output === void 0 || output.length === 0) {
569
- rejectProbe(
570
- createGtkOperationFailedError(
571
- "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
572
979
  )
573
980
  );
574
981
  return;
575
982
  }
576
983
  try {
577
- resolveProbe(JSON.parse(output));
984
+ resolveOnce(JSON.parse(output));
578
985
  } catch (error) {
579
986
  const message = error instanceof Error ? error.message : String(error);
580
- rejectProbe(
581
- createGtkOperationFailedError(
582
- `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
583
991
  )
584
992
  );
585
993
  }
586
994
  });
587
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
+ };
588
1023
  const cleanCheckXvfb = async (xvfb, allowedMappedWindowCount) => {
589
1024
  try {
590
1025
  const probe = await runXvfbProbe(xvfb);
@@ -603,7 +1038,7 @@ const returnXvfbToPool = async (xvfb, limits) => {
603
1038
  };
604
1039
  const allPoolKey = (xvfb) => `${xvfb.screen}
605
1040
  ${xvfb.trayHost ? "tray" : "no-tray"}`;
606
- const spawnDriverProcess = (driverPath, socketPath, effective, xvfb) => {
1041
+ const spawnDriverProcess = (driverPath, socketPath, effective, xvfb, systemOutputSink) => {
607
1042
  const driverArgs = [
608
1043
  "--socket",
609
1044
  socketPath,
@@ -636,14 +1071,46 @@ const spawnDriverProcess = (driverPath, socketPath, effective, xvfb) => {
636
1071
  env,
637
1072
  stdio: ["ignore", "pipe", "pipe"]
638
1073
  });
1074
+ const processState = {
1075
+ child,
1076
+ commandLine: [command.bin, ...command.args].join(" "),
1077
+ stderr,
1078
+ stdout,
1079
+ systemOutputSink
1080
+ };
639
1081
  child.stdout.on("data", (chunk) => {
640
1082
  appendOutput$1(stdout, chunk);
1083
+ appendSystemOutput(
1084
+ processState.systemOutputSink,
1085
+ "launcher-driver",
1086
+ "stdout",
1087
+ chunk
1088
+ );
641
1089
  });
642
1090
  child.stderr.on("data", (chunk) => {
643
1091
  appendOutput$1(stderr, chunk);
1092
+ appendSystemOutput(
1093
+ processState.systemOutputSink,
1094
+ "launcher-driver",
1095
+ "stderr",
1096
+ chunk
1097
+ );
644
1098
  });
645
- const commandLine = [command.bin, ...command.args].join(" ");
646
- return { child, commandLine, stderr, stdout };
1099
+ child.stdout.once("end", () => {
1100
+ flushSystemOutput(
1101
+ processState.systemOutputSink,
1102
+ "launcher-driver",
1103
+ "stdout"
1104
+ );
1105
+ });
1106
+ child.stderr.once("end", () => {
1107
+ flushSystemOutput(
1108
+ processState.systemOutputSink,
1109
+ "launcher-driver",
1110
+ "stderr"
1111
+ );
1112
+ });
1113
+ return processState;
647
1114
  };
648
1115
  const listenOnSocket = (server, socketPath) => new Promise((resolveListen, rejectListen) => {
649
1116
  const rejectFromError = (error) => {
@@ -722,7 +1189,13 @@ const waitForDriverReady = (server, processState) => new Promise((resolveReady,
722
1189
  const line = input.slice(0, newlineIndex);
723
1190
  input = input.slice(newlineIndex + 1);
724
1191
  try {
725
- 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) {
726
1199
  if (settled) {
727
1200
  return;
728
1201
  }
@@ -800,6 +1273,7 @@ const decodeCapture = (capture) => ({
800
1273
  visibleBounds: capture.visibleBounds
801
1274
  });
802
1275
  const createDriverSession = (socket, bufferedInput, processState, tempDirectory, poolOptions) => {
1276
+ const eventHandlers = /* @__PURE__ */ new Map();
803
1277
  const pending = /* @__PURE__ */ new Map();
804
1278
  let input = bufferedInput;
805
1279
  let nextRequestId = 1;
@@ -819,8 +1293,34 @@ const createDriverSession = (socket, bufferedInput, processState, tempDirectory,
819
1293
  closed = true;
820
1294
  rejectPending(createGtkAppExitedError("Launcher driver has exited."));
821
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
+ };
822
1317
  const handleResponse = (line) => {
823
- 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;
824
1324
  const entry = pending.get(response.id);
825
1325
  if (entry === void 0) {
826
1326
  return;
@@ -895,6 +1395,28 @@ const createDriverSession = (socket, bufferedInput, processState, tempDirectory,
895
1395
  });
896
1396
  });
897
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
+ };
898
1420
  const waitForExit = async () => {
899
1421
  const startedAt = Date.now();
900
1422
  while (processState.child.exitCode === null && processState.child.signalCode === null) {
@@ -979,10 +1501,10 @@ const createDriverSession = (socket, bufferedInput, processState, tempDirectory,
979
1501
  poolOptions.limits
980
1502
  );
981
1503
  };
982
- session = { release, request, terminate };
1504
+ session = { release, request, setSystemOutputSink, subscribe, terminate };
983
1505
  return session;
984
1506
  };
985
- const startFreshDriverSession = async (effective, xvfb, poolOptions) => {
1507
+ const startFreshDriverSession = async (effective, xvfb, poolOptions, systemOutputSink) => {
986
1508
  const driverPath = resolveDriverPath();
987
1509
  const tempDirectory = mkdtempSync(
988
1510
  join(tmpdir(), `gestament-launcher-${process.pid}-${socketCounter}-`)
@@ -994,7 +1516,13 @@ const startFreshDriverSession = async (effective, xvfb, poolOptions) => {
994
1516
  try {
995
1517
  await listenOnSocket(server, socketPath);
996
1518
  server.unref();
997
- processState = spawnDriverProcess(driverPath, socketPath, effective, xvfb);
1519
+ processState = spawnDriverProcess(
1520
+ driverPath,
1521
+ socketPath,
1522
+ effective,
1523
+ xvfb,
1524
+ systemOutputSink
1525
+ );
998
1526
  const connection = await waitForDriverReady(server, processState);
999
1527
  server.close();
1000
1528
  const resolvedPoolOptions = poolOptions.mode === "all" && xvfb !== void 0 ? {
@@ -1020,7 +1548,7 @@ const startFreshDriverSession = async (effective, xvfb, poolOptions) => {
1020
1548
  throw error;
1021
1549
  }
1022
1550
  };
1023
- const startDriverSession = async (options) => {
1551
+ const startDriverSession = async (options, systemOutputSink) => {
1024
1552
  const display = resolveDisplay(options.display);
1025
1553
  const xvfbOptions = resolveXvfbOptions(options);
1026
1554
  const effective = resolveEffectiveDisplay(display, xvfbOptions);
@@ -1028,7 +1556,8 @@ const startDriverSession = async (options) => {
1028
1556
  return startFreshDriverSession(
1029
1557
  effective,
1030
1558
  void 0,
1031
- emptyDriverSessionPoolOptions()
1559
+ emptyDriverSessionPoolOptions(),
1560
+ systemOutputSink
1032
1561
  );
1033
1562
  }
1034
1563
  const pool = effective.xvfb.pool;
@@ -1036,18 +1565,24 @@ const startDriverSession = async (options) => {
1036
1565
  return startFreshDriverSession(
1037
1566
  effective,
1038
1567
  void 0,
1039
- emptyDriverSessionPoolOptions()
1568
+ emptyDriverSessionPoolOptions(),
1569
+ systemOutputSink
1040
1570
  );
1041
1571
  }
1042
1572
  if (pool.type === "xvfb") {
1043
- const xvfb2 = await leaseXvfb(effective.xvfb.screen);
1044
- return startFreshDriverSession(effective, xvfb2, {
1045
- allKey: void 0,
1046
- allowedMappedWindowCount: 0,
1047
- limits: poolLimits(pool),
1048
- mode: "xvfb",
1049
- xvfb: xvfb2
1050
- });
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
+ );
1051
1586
  }
1052
1587
  const key = allPoolKey(effective.xvfb);
1053
1588
  for (; ; ) {
@@ -1058,20 +1593,27 @@ const startDriverSession = async (options) => {
1058
1593
  if (idle.xvfb.child.exitCode === null && idle.xvfb.child.signalCode === null) {
1059
1594
  idle.lastUsedAt = Date.now();
1060
1595
  idle.xvfb.lastUsedAt = idle.lastUsedAt;
1596
+ idle.xvfb.systemOutputSink = systemOutputSink;
1597
+ idle.session.setSystemOutputSink(systemOutputSink);
1061
1598
  return idle.session;
1062
1599
  }
1063
1600
  await idle.session.terminate().catch(() => void 0);
1064
1601
  }
1065
- const xvfb = await leaseXvfb(effective.xvfb.screen);
1066
- return startFreshDriverSession(effective, xvfb, {
1067
- allKey: key,
1068
- allowedMappedWindowCount: 0,
1069
- limits: poolLimits(pool),
1070
- mode: "all",
1071
- xvfb
1072
- });
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
+ );
1073
1615
  };
1074
- const createLaunchPayload = (options, args) => {
1616
+ const createLaunchPayload = (options, args, launchOptions, outputScopeId) => {
1075
1617
  const display = resolveDisplay(options.display);
1076
1618
  const xvfb = resolveXvfbOptions(options);
1077
1619
  const effective = resolveEffectiveDisplay(display, xvfb);
@@ -1079,12 +1621,25 @@ const createLaunchPayload = (options, args) => {
1079
1621
  appPath: options.appPath,
1080
1622
  args: [...options.args ?? [], ...args],
1081
1623
  env: resolveLauncherEnvironment(options, effective),
1624
+ outputBufferBytes: resolveOutputBufferBytes(
1625
+ options.outputBufferBytes,
1626
+ launchOptions?.outputBufferBytes
1627
+ ),
1628
+ outputScopeId,
1082
1629
  timeoutMs: options.timeoutMs ?? null
1083
1630
  };
1084
1631
  };
1632
+ const createEnvironmentPayload = (options) => {
1633
+ const display = resolveDisplay(options.display);
1634
+ const xvfb = resolveXvfbOptions(options);
1635
+ const effective = resolveEffectiveDisplay(display, xvfb);
1636
+ return {
1637
+ env: resolveLauncherEnvironment(options, effective)
1638
+ };
1639
+ };
1085
1640
  const elementRefToProxy = (session, ref) => ref === null ? void 0 : createProxyGtkElement(session, ref);
1086
1641
  const trayRefToProxy = (session, ref) => ref === null ? void 0 : createProxyGtkTrayItem(session, ref);
1087
- const createProxyGtkApp = (session, ref) => {
1642
+ const createProxyGtkApp = (session, ref, outputSubscription) => {
1088
1643
  let released = false;
1089
1644
  const assertNotReleased = () => {
1090
1645
  if (released) {
@@ -1103,10 +1658,15 @@ const createProxyGtkApp = (session, ref) => {
1103
1658
  return;
1104
1659
  }
1105
1660
  released = true;
1661
+ outputSubscription?.dispose();
1106
1662
  await session.request("app.release", { appId: ref.appId });
1107
1663
  };
1108
1664
  const app = {
1109
1665
  capture: async () => decodeCapture(await appRequest("app.capture")),
1666
+ environment: async () => wireEnvironmentToGtkAppEnvironment(
1667
+ await appRequest("app.environment")
1668
+ ),
1669
+ output: () => appRequest("app.output"),
1110
1670
  findById: async (id) => elementRefToProxy(
1111
1671
  session,
1112
1672
  await appRequest("app.findById", { id })
@@ -1219,6 +1779,13 @@ const createProxyGtkElement = (session, ref) => {
1219
1779
  };
1220
1780
  switch (ref.kind) {
1221
1781
  case "window":
1782
+ target.bounds = () => session.request("element.bounds", { elementId });
1783
+ target.resizeHints = () => session.request("window.resizeHints", {
1784
+ elementId
1785
+ });
1786
+ target.x11Info = () => session.request("window.x11Info", { elementId });
1787
+ addChildContainerProxyOperations(session, elementId, target);
1788
+ break;
1222
1789
  case "container":
1223
1790
  case "menu":
1224
1791
  addChildContainerProxyOperations(session, elementId, target);
@@ -1327,19 +1894,84 @@ const createProxyGtkTrayItem = (session, ref) => {
1327
1894
  };
1328
1895
  };
1329
1896
  const createDriverBackedGtkAppLauncher = (options) => {
1897
+ const outputSubscriptions = /* @__PURE__ */ new Set();
1898
+ let systemOutputRecorder = createGtkSystemOutputRecorder(
1899
+ normalizeOutputBufferBytes(
1900
+ options.systemOutputBufferBytes,
1901
+ "systemOutputBufferBytes"
1902
+ )
1903
+ );
1330
1904
  let sessionPromise;
1331
1905
  const ensureSession = () => {
1332
- sessionPromise ??= startDriverSession(options);
1906
+ if (sessionPromise === void 0) {
1907
+ systemOutputRecorder = createGtkSystemOutputRecorder(
1908
+ normalizeOutputBufferBytes(
1909
+ options.systemOutputBufferBytes,
1910
+ "systemOutputBufferBytes"
1911
+ )
1912
+ );
1913
+ sessionPromise = startDriverSession(
1914
+ options,
1915
+ createSystemOutputSink(systemOutputRecorder, options.onSystemOutput)
1916
+ );
1917
+ }
1333
1918
  return sessionPromise;
1334
1919
  };
1335
- const launch = async (args) => {
1920
+ const trackOutputSubscription = (subscription) => {
1921
+ let tracked;
1922
+ tracked = {
1923
+ dispose: () => {
1924
+ if (!outputSubscriptions.delete(tracked)) {
1925
+ return;
1926
+ }
1927
+ subscription.dispose();
1928
+ }
1929
+ };
1930
+ outputSubscriptions.add(tracked);
1931
+ return tracked;
1932
+ };
1933
+ const disposeOutputSubscriptions = () => {
1934
+ for (const subscription of [...outputSubscriptions]) {
1935
+ subscription.dispose();
1936
+ }
1937
+ };
1938
+ const launch = async (args, launchOptions) => {
1336
1939
  const session = await ensureSession();
1337
- const appRef = await session.request(
1338
- "launcher.launch",
1339
- createLaunchPayload(options, args ?? [])
1940
+ const onOutput = launchOptions?.onOutput;
1941
+ const outputScopeId = onOutput === void 0 ? null : createOutputScopeId();
1942
+ const outputSubscription = outputScopeId === null ? void 0 : trackOutputSubscription(
1943
+ session.subscribe("app.output", outputScopeId, (value) => {
1944
+ onOutput?.(value);
1945
+ })
1946
+ );
1947
+ const payload = createLaunchPayload(
1948
+ options,
1949
+ args ?? [],
1950
+ launchOptions,
1951
+ outputScopeId
1340
1952
  );
1341
- return createProxyGtkApp(session, appRef);
1953
+ try {
1954
+ const appRef = await session.request(
1955
+ "launcher.launch",
1956
+ payload
1957
+ );
1958
+ return createProxyGtkApp(session, appRef, outputSubscription);
1959
+ } catch (error) {
1960
+ outputSubscription?.dispose();
1961
+ throw error;
1962
+ }
1342
1963
  };
1964
+ const environment = async () => {
1965
+ const payload = createEnvironmentPayload(options);
1966
+ const session = await ensureSession();
1967
+ return wireEnvironmentToGtkAppEnvironment(
1968
+ await session.request(
1969
+ "launcher.environment",
1970
+ payload
1971
+ )
1972
+ );
1973
+ };
1974
+ const systemOutput = () => Promise.resolve(systemOutputRecorder.snapshot());
1343
1975
  const release = async () => {
1344
1976
  const releasingSession = sessionPromise;
1345
1977
  sessionPromise = void 0;
@@ -1347,11 +1979,17 @@ const createDriverBackedGtkAppLauncher = (options) => {
1347
1979
  return;
1348
1980
  }
1349
1981
  const session = await releasingSession;
1350
- await session.release();
1982
+ try {
1983
+ await session.release();
1984
+ } finally {
1985
+ disposeOutputSubscriptions();
1986
+ }
1351
1987
  };
1352
1988
  return {
1989
+ environment,
1353
1990
  launch,
1354
1991
  release,
1992
+ systemOutput,
1355
1993
  [Symbol.asyncDispose]: release
1356
1994
  };
1357
1995
  };
@@ -1758,6 +2396,9 @@ const createImageInfoOperation = (handle) => async () => {
1758
2396
  capture: async () => nativeCaptureBounds(info.bounds)
1759
2397
  };
1760
2398
  };
2399
+ const createBoundsOperation = (handle) => async () => nativeBounds(handle);
2400
+ const createResizeHintsOperation = (handle) => async () => nativeResizeHints(handle);
2401
+ const createX11InfoOperation = (handle) => async () => nativeX11Info(handle);
1761
2402
  const createValueOperation = (handle) => async () => nativeValueInfo(handle).value;
1762
2403
  const createSetValueOperation = (handle) => async (value) => {
1763
2404
  assertFiniteNumber("value", value);
@@ -1929,7 +2570,10 @@ const createGtkElement = (handle) => {
1929
2570
  return {
1930
2571
  ...common,
1931
2572
  kind: "window",
1932
- ...createChildContainerOperations(handle, void 0)
2573
+ bounds: createBoundsOperation(handle),
2574
+ ...createChildContainerOperations(handle, void 0),
2575
+ resizeHints: createResizeHintsOperation(handle),
2576
+ x11Info: createX11InfoOperation(handle)
1933
2577
  };
1934
2578
  case "button":
1935
2579
  return { ...common, kind: "button", click: createClickOperation(handle) };
@@ -2257,30 +2901,70 @@ const createGtkAppEnvironment = (baseEnv, overrides) => {
2257
2901
  const launchGtkApp = (appPath, args, options) => {
2258
2902
  const _args = args ?? [];
2259
2903
  const _timeoutMs = options?.timeoutMs ?? 1e4;
2904
+ const outputBufferBytes = normalizeOutputBufferBytes(
2905
+ options?.outputBufferBytes
2906
+ );
2907
+ const outputRecorder = createGtkAppOutputRecorder(outputBufferBytes);
2908
+ const appEnvironment = createGtkAppEnvironment(process.env, options?.env);
2260
2909
  const child = spawn(appPath, [..._args], {
2261
- env: createGtkAppEnvironment(process.env, options?.env),
2910
+ env: appEnvironment,
2262
2911
  stdio: "pipe"
2263
2912
  });
2913
+ let resolveClosed;
2914
+ const closedPromise = new Promise((resolve2) => {
2915
+ resolveClosed = resolve2;
2916
+ });
2264
2917
  const state = {
2265
2918
  atspiReadiness: "missing-bus-name",
2266
2919
  atspiReady: false,
2920
+ closed: false,
2921
+ closedPromise,
2267
2922
  exitCode: null,
2268
2923
  exitSignal: null,
2269
2924
  process: child,
2925
+ released: false,
2270
2926
  stderr: [],
2271
2927
  stdout: []
2272
2928
  };
2929
+ const notifyOutput = (stream, chunk) => {
2930
+ notifyGtkAppOutput(options?.onOutput, outputRecorder.append(stream, chunk));
2931
+ };
2932
+ const flushOutput = (stream) => {
2933
+ notifyGtkAppOutput(options?.onOutput, outputRecorder.flush(stream));
2934
+ };
2273
2935
  child.stdout.on("data", (chunk) => {
2274
2936
  appendOutput(state.stdout, chunk);
2937
+ notifyOutput("stdout", chunk);
2275
2938
  });
2276
2939
  child.stderr.on("data", (chunk) => {
2277
2940
  appendOutput(state.stderr, chunk);
2941
+ notifyOutput("stderr", chunk);
2942
+ });
2943
+ child.stdout.on("end", () => {
2944
+ flushOutput("stdout");
2945
+ });
2946
+ child.stderr.on("end", () => {
2947
+ flushOutput("stderr");
2278
2948
  });
2279
2949
  child.on("exit", (code, signal) => {
2280
2950
  state.exitCode = code;
2281
2951
  state.exitSignal = signal;
2282
2952
  });
2953
+ child.on("close", (code, signal) => {
2954
+ if (state.exitCode === null && state.exitSignal === null) {
2955
+ state.exitCode = code;
2956
+ state.exitSignal = signal;
2957
+ }
2958
+ flushOutput("stdout");
2959
+ flushOutput("stderr");
2960
+ state.closed = true;
2961
+ resolveClosed?.();
2962
+ });
2283
2963
  const release = async () => {
2964
+ if (state.released) {
2965
+ return;
2966
+ }
2967
+ state.released = true;
2284
2968
  if (state.exitCode !== null || state.exitSignal !== null) {
2285
2969
  return;
2286
2970
  }
@@ -2294,6 +2978,11 @@ const launchGtkApp = (appPath, args, options) => {
2294
2978
  await delay$1(25);
2295
2979
  }
2296
2980
  };
2981
+ const assertNotReleased = () => {
2982
+ if (state.released) {
2983
+ throw createGtkAppExitedError("GTK application has been released.");
2984
+ }
2985
+ };
2297
2986
  const findById = async (id) => {
2298
2987
  const startedAt = Date.now();
2299
2988
  const timeoutMs = effectiveWaitTimeoutMs(_timeoutMs);
@@ -2401,6 +3090,7 @@ const launchGtkApp = (appPath, args, options) => {
2401
3090
  };
2402
3091
  const app = {
2403
3092
  capture: async () => {
3093
+ assertNotReleased();
2404
3094
  assertProcessRunning(state, appPath);
2405
3095
  try {
2406
3096
  return nativeCaptureScreen();
@@ -2408,6 +3098,21 @@ const launchGtkApp = (appPath, args, options) => {
2408
3098
  throw normalizeNativeError(error);
2409
3099
  }
2410
3100
  },
3101
+ environment: async () => {
3102
+ assertNotReleased();
3103
+ assertProcessRunning(state, appPath);
3104
+ return { ...appEnvironment };
3105
+ },
3106
+ output: async () => {
3107
+ assertNotReleased();
3108
+ if ((state.exitCode !== null || state.exitSignal !== null) && !state.closed) {
3109
+ await state.closedPromise;
3110
+ }
3111
+ return outputRecorder.snapshot(
3112
+ state.exitCode,
3113
+ state.exitSignal === null ? null : state.exitSignal
3114
+ );
3115
+ },
2411
3116
  findById,
2412
3117
  findByPath,
2413
3118
  getById,
@@ -2476,4 +3181,4 @@ export {
2476
3181
  createGtkAppEnvironment as c,
2477
3182
  launchGtkApp as l
2478
3183
  };
2479
- //# sourceMappingURL=launchGtkApp-qi1qm5G4.js.map
3184
+ //# sourceMappingURL=launchGtkApp-EI6OIpPI.js.map