gestament 0.2.0 → 0.4.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 (87) hide show
  1. package/README.md +6 -6
  2. package/dist/displaySession.d.ts +18 -0
  3. package/dist/displaySession.d.ts.map +1 -0
  4. package/dist/element.d.ts +2 -2
  5. package/dist/element.d.ts.map +1 -1
  6. package/dist/errors-6gj5YuLw.cjs +36 -0
  7. package/dist/errors-6gj5YuLw.cjs.map +1 -0
  8. package/dist/errors-CCW4ATME.js +37 -0
  9. package/dist/errors-CCW4ATME.js.map +1 -0
  10. package/dist/errors.d.ts +2 -2
  11. package/dist/errors.d.ts.map +1 -1
  12. package/dist/generated/packageMetadata.d.ts +4 -4
  13. package/dist/gestament-config.d.ts +2 -2
  14. package/dist/gestament-launcher-driver.cjs +683 -0
  15. package/dist/gestament-launcher-driver.cjs.map +1 -0
  16. package/dist/gestament-launcher-driver.d.ts +13 -0
  17. package/dist/gestament-launcher-driver.d.ts.map +1 -0
  18. package/dist/gestament-launcher-driver.mjs +682 -0
  19. package/dist/gestament-launcher-driver.mjs.map +1 -0
  20. package/dist/gestament-tray-host.cjs +1 -1
  21. package/dist/gestament-tray-host.d.ts +2 -2
  22. package/dist/gestament-tray-host.mjs +1 -1
  23. package/dist/gestament-xvfb-pool-probe.cjs +29 -0
  24. package/dist/gestament-xvfb-pool-probe.cjs.map +1 -0
  25. package/dist/gestament-xvfb-pool-probe.d.ts +13 -0
  26. package/dist/gestament-xvfb-pool-probe.d.ts.map +1 -0
  27. package/dist/gestament-xvfb-pool-probe.mjs +28 -0
  28. package/dist/gestament-xvfb-pool-probe.mjs.map +1 -0
  29. package/dist/gestament-xvfb-worker.d.ts +2 -2
  30. package/dist/gestament-xvfb.cjs +15 -4
  31. package/dist/gestament-xvfb.cjs.map +1 -1
  32. package/dist/gestament-xvfb.d.ts +2 -2
  33. package/dist/gestament-xvfb.mjs +15 -4
  34. package/dist/gestament-xvfb.mjs.map +1 -1
  35. package/dist/index.cjs +4 -1147
  36. package/dist/index.cjs.map +1 -1
  37. package/dist/index.d.ts +2 -2
  38. package/dist/index.mjs +4 -1147
  39. package/dist/index.mjs.map +1 -1
  40. package/dist/launchGtkApp-BfELuV-H.cjs +2567 -0
  41. package/dist/launchGtkApp-BfELuV-H.cjs.map +1 -0
  42. package/dist/launchGtkApp-Bst1BFbD.js +2567 -0
  43. package/dist/launchGtkApp-Bst1BFbD.js.map +1 -0
  44. package/dist/launchGtkApp.d.ts +2 -2
  45. package/dist/launchGtkApp.d.ts.map +1 -1
  46. package/dist/launcherDriverProtocol.d.ts +110 -0
  47. package/dist/launcherDriverProtocol.d.ts.map +1 -0
  48. package/dist/{native-ie_XIt1J.cjs → native-BUWDWMBB.cjs} +20 -40
  49. package/dist/native-BUWDWMBB.cjs.map +1 -0
  50. package/dist/{native-DUeVYIBs.js → native-C6MsIBNF.js} +52 -72
  51. package/dist/native-C6MsIBNF.js.map +1 -0
  52. package/dist/native.d.ts +25 -2
  53. package/dist/native.d.ts.map +1 -1
  54. package/dist/prerequisites-BuZST2Dy.cjs +15 -0
  55. package/dist/prerequisites-BuZST2Dy.cjs.map +1 -0
  56. package/dist/prerequisites-JB0SKPVd.js +16 -0
  57. package/dist/prerequisites-JB0SKPVd.js.map +1 -0
  58. package/dist/prerequisites.d.ts +23 -0
  59. package/dist/prerequisites.d.ts.map +1 -0
  60. package/dist/testing.cjs +3 -0
  61. package/dist/testing.cjs.map +1 -1
  62. package/dist/testing.d.ts +4 -2
  63. package/dist/testing.d.ts.map +1 -1
  64. package/dist/testing.mjs +4 -1
  65. package/dist/testing.mjs.map +1 -1
  66. package/dist/tray.d.ts +2 -2
  67. package/dist/types.d.ts +158 -2
  68. package/dist/types.d.ts.map +1 -1
  69. package/dist/wait-DV5gkXs8.js +113 -0
  70. package/dist/wait-DV5gkXs8.js.map +1 -0
  71. package/dist/wait-eOIz4nZm.cjs +112 -0
  72. package/dist/wait-eOIz4nZm.cjs.map +1 -0
  73. package/dist/wait.d.ts +67 -0
  74. package/dist/wait.d.ts.map +1 -0
  75. package/package.json +7 -7
  76. package/prebuilds/linux-arm/gtk3/node.napi.armv7.glibc.node +0 -0
  77. package/prebuilds/linux-arm/gtk4/node.napi.armv7.glibc.node +0 -0
  78. package/prebuilds/linux-arm64/gtk3/node.napi.glibc.node +0 -0
  79. package/prebuilds/linux-arm64/gtk4/node.napi.glibc.node +0 -0
  80. package/prebuilds/linux-ia32/gtk3/node.napi.glibc.node +0 -0
  81. package/prebuilds/linux-ia32/gtk4/node.napi.glibc.node +0 -0
  82. package/prebuilds/linux-riscv64/gtk3/node.napi.glibc.node +0 -0
  83. package/prebuilds/linux-riscv64/gtk4/node.napi.glibc.node +0 -0
  84. package/prebuilds/linux-x64/gtk3/node.napi.glibc.node +0 -0
  85. package/prebuilds/linux-x64/gtk4/node.napi.glibc.node +0 -0
  86. package/dist/native-DUeVYIBs.js.map +0 -1
  87. package/dist/native-ie_XIt1J.cjs.map +0 -1
@@ -0,0 +1,2567 @@
1
+ import { spawn } from "node:child_process";
2
+ import { c as currentWaitDeadlineMs, e as effectiveWaitTimeoutMs, d as delay$1 } from "./wait-DV5gkXs8.js";
3
+ import { f as createGtkInvalidArgumentError, c as createGtkOperationFailedError, a as createGtkAppExitedError, d as createGtkUnsupportedInterfaceError, g as createGtkElementNotFoundError, b as createGtkStaleElementError, n as normalizeNativeError } from "./errors-CCW4ATME.js";
4
+ import { mkdtempSync, rmSync, existsSync } from "node:fs";
5
+ import { createServer, createConnection } from "node:net";
6
+ import { tmpdir } from "node:os";
7
+ import { join, resolve, dirname } from "node:path";
8
+ import { fileURLToPath } from "node:url";
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";
11
+ const defaultDisplay = "xvfb";
12
+ const defaultGSettings = "memory";
13
+ const defaultTheme = "Adwaita";
14
+ const defaultXvfbScreen = "1280x720x24";
15
+ const defaultXvfbTrayHost = true;
16
+ const defaultXvfbPoolMaxIdlePerKey = 1;
17
+ const defaultXvfbPoolMaxIdleTotal = 4;
18
+ const screenPattern = /^[1-9][0-9]*x[1-9][0-9]*x[1-9][0-9]*$/;
19
+ const sessionStartupTimeoutMs = 3e4;
20
+ const sessionReleaseTimeoutMs = 5e3;
21
+ const xvfbStartupTimeoutMs = 1e4;
22
+ const xvfbPoolProbeTimeoutMs = 5e3;
23
+ const firstPooledDisplayNumber = 90;
24
+ const lastPooledDisplayNumber = 590;
25
+ const sessionOwnedEnvironmentKeys = [
26
+ "DISPLAY",
27
+ "WAYLAND_DISPLAY",
28
+ "GDK_BACKEND",
29
+ "DBUS_SESSION_BUS_ADDRESS",
30
+ "AT_SPI_BUS_ADDRESS",
31
+ "NO_AT_BRIDGE",
32
+ "XAUTHORITY",
33
+ "GESTAMENT_XVFB_ACTIVE",
34
+ "XDG_SESSION_TYPE"
35
+ ];
36
+ let socketCounter = 0;
37
+ const leasedDisplayNumbers = /* @__PURE__ */ new Set();
38
+ const idleXvfbByKey = /* @__PURE__ */ new Map();
39
+ const idleAllByKey = /* @__PURE__ */ new Map();
40
+ const directXvfbs = /* @__PURE__ */ new Set();
41
+ let poolCleanupInstalled = false;
42
+ const hasValue = (value) => value !== void 0 && value.length > 0;
43
+ const delay = (timeoutMs) => new Promise((resolveDelay) => {
44
+ setTimeout(resolveDelay, timeoutMs);
45
+ });
46
+ const appendOutput$1 = (lines, chunk) => {
47
+ lines.push(chunk.toString("utf8"));
48
+ if (lines.length > 40) {
49
+ lines.splice(0, lines.length - 40);
50
+ }
51
+ };
52
+ const unrefHandle = (handle) => {
53
+ const maybeRefHandle = handle;
54
+ if (typeof maybeRefHandle.unref === "function") {
55
+ maybeRefHandle.unref();
56
+ }
57
+ };
58
+ const formatOutputTail = (stdout, stderr) => {
59
+ const stdoutText = stdout.join("").trim();
60
+ const stderrText = stderr.join("").trim();
61
+ if (stdoutText.length === 0 && stderrText.length === 0) {
62
+ return "";
63
+ }
64
+ return `
65
+ stdout:
66
+ ${stdoutText}
67
+ stderr:
68
+ ${stderrText}`;
69
+ };
70
+ const getHostDisplayState = () => ({
71
+ display: process.env.DISPLAY,
72
+ waylandDisplay: process.env.WAYLAND_DISPLAY
73
+ });
74
+ const resolveHostDisplayKind = (state) => {
75
+ if (hasValue(state.waylandDisplay)) {
76
+ return "wayland";
77
+ }
78
+ if (hasValue(state.display)) {
79
+ return "x11";
80
+ }
81
+ return void 0;
82
+ };
83
+ const resolveDisplay = (display) => {
84
+ if (display === void 0) {
85
+ return defaultDisplay;
86
+ }
87
+ if (display === "xvfb" || display === "host") {
88
+ return display;
89
+ }
90
+ throw createGtkInvalidArgumentError(
91
+ `display must be "xvfb" or "host": ${String(display)}`
92
+ );
93
+ };
94
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
95
+ const resolvePoolLimit = (name, value, defaultValue) => {
96
+ if (value === void 0) {
97
+ return defaultValue;
98
+ }
99
+ if (!Number.isInteger(value) || value < 0) {
100
+ throw createGtkInvalidArgumentError(`${name} must be an integer >= 0.`);
101
+ }
102
+ return value;
103
+ };
104
+ const resolveXvfbPool = (pool) => {
105
+ if (pool === void 0) {
106
+ return void 0;
107
+ }
108
+ if (!isRecord(pool)) {
109
+ throw createGtkInvalidArgumentError(
110
+ 'xvfbPool must be an object with type "xvfb" or "all".'
111
+ );
112
+ }
113
+ const { type } = pool;
114
+ if (type !== "xvfb" && type !== "all") {
115
+ throw createGtkInvalidArgumentError(
116
+ `xvfbPool.type must be "xvfb" or "all": ${String(type)}`
117
+ );
118
+ }
119
+ return {
120
+ maxIdlePerKey: resolvePoolLimit(
121
+ "xvfbPool.maxIdlePerKey",
122
+ pool.maxIdlePerKey,
123
+ defaultXvfbPoolMaxIdlePerKey
124
+ ),
125
+ maxIdleTotal: resolvePoolLimit(
126
+ "xvfbPool.maxIdleTotal",
127
+ pool.maxIdleTotal,
128
+ defaultXvfbPoolMaxIdleTotal
129
+ ),
130
+ type
131
+ };
132
+ };
133
+ const nonePoolLimits = () => ({
134
+ maxIdlePerKey: 0,
135
+ maxIdleTotal: 0
136
+ });
137
+ const poolLimits = (pool) => ({
138
+ maxIdlePerKey: pool.maxIdlePerKey,
139
+ maxIdleTotal: pool.maxIdleTotal
140
+ });
141
+ const shouldRetainIdlePool = (limits) => limits.maxIdlePerKey > 0 && limits.maxIdleTotal > 0;
142
+ const removeArrayEntry = (map, key, entry) => {
143
+ const entries = map.get(key);
144
+ if (entries === void 0) {
145
+ return;
146
+ }
147
+ const index = entries.indexOf(entry);
148
+ if (index >= 0) {
149
+ entries.splice(index, 1);
150
+ }
151
+ if (entries.length === 0) {
152
+ map.delete(key);
153
+ }
154
+ };
155
+ const pushArrayEntry = (map, key, entry) => {
156
+ const entries = map.get(key);
157
+ if (entries === void 0) {
158
+ map.set(key, [entry]);
159
+ return;
160
+ }
161
+ entries.push(entry);
162
+ };
163
+ const popArrayEntry = (map, key) => {
164
+ const entries = map.get(key);
165
+ if (entries === void 0) {
166
+ return void 0;
167
+ }
168
+ const entry = entries.pop();
169
+ if (entries.length === 0) {
170
+ map.delete(key);
171
+ }
172
+ return entry;
173
+ };
174
+ const totalArrayEntryCount = (map) => [...map.values()].reduce((total, entries) => total + entries.length, 0);
175
+ const oldestEntry = (entries) => [...entries].sort((first, second) => first.lastUsedAt - second.lastUsedAt)[0];
176
+ const allIdleXvfbs = () => [...idleXvfbByKey.values()].flat();
177
+ const allIdleAllSessions = () => [...idleAllByKey.values()].flat();
178
+ const totalIdlePoolSize = () => totalArrayEntryCount(idleXvfbByKey) + totalArrayEntryCount(idleAllByKey);
179
+ const candidateOldestIdlePool = () => {
180
+ const candidates = [
181
+ ...allIdleXvfbs().map((xvfb) => ({
182
+ kind: "xvfb",
183
+ lastUsedAt: xvfb.lastUsedAt,
184
+ xvfb
185
+ })),
186
+ ...allIdleAllSessions().map((session) => ({
187
+ kind: "all",
188
+ lastUsedAt: session.lastUsedAt,
189
+ session
190
+ }))
191
+ ].sort((first, second) => first.lastUsedAt - second.lastUsedAt);
192
+ const candidate = candidates[0];
193
+ if (candidate === void 0) {
194
+ return void 0;
195
+ }
196
+ return candidate.kind === "xvfb" ? { kind: "xvfb", xvfb: candidate.xvfb } : { kind: "all", session: candidate.session };
197
+ };
198
+ const evictIdleXvfb = async (xvfb) => {
199
+ removeArrayEntry(idleXvfbByKey, xvfb.key, xvfb);
200
+ await terminateXvfb(xvfb);
201
+ };
202
+ const evictIdleAllSession = async (session) => {
203
+ removeArrayEntry(idleAllByKey, session.key, session);
204
+ await session.session.terminate();
205
+ };
206
+ const evictIdlePools = async (maxIdleTotal) => {
207
+ while (totalIdlePoolSize() > maxIdleTotal) {
208
+ const candidate = candidateOldestIdlePool();
209
+ if (candidate === void 0) {
210
+ return;
211
+ }
212
+ if (candidate.kind === "xvfb") {
213
+ await evictIdleXvfb(candidate.xvfb);
214
+ } else {
215
+ await evictIdleAllSession(candidate.session);
216
+ }
217
+ }
218
+ };
219
+ const trimIdleXvfbKey = async (key, maxIdlePerKey) => {
220
+ while ((idleXvfbByKey.get(key)?.length ?? 0) > maxIdlePerKey) {
221
+ const entry = oldestEntry(idleXvfbByKey.get(key) ?? []);
222
+ if (entry === void 0) {
223
+ return;
224
+ }
225
+ await evictIdleXvfb(entry);
226
+ }
227
+ };
228
+ const trimIdleAllKey = async (key, maxIdlePerKey) => {
229
+ while ((idleAllByKey.get(key)?.length ?? 0) > maxIdlePerKey) {
230
+ const entry = oldestEntry(idleAllByKey.get(key) ?? []);
231
+ if (entry === void 0) {
232
+ return;
233
+ }
234
+ await evictIdleAllSession(entry);
235
+ }
236
+ };
237
+ const retainIdleXvfb = async (xvfb, limits) => {
238
+ if (!shouldRetainIdlePool(limits)) {
239
+ await terminateXvfb(xvfb);
240
+ return;
241
+ }
242
+ xvfb.lastUsedAt = Date.now();
243
+ pushArrayEntry(idleXvfbByKey, xvfb.key, xvfb);
244
+ await trimIdleXvfbKey(xvfb.key, limits.maxIdlePerKey);
245
+ await evictIdlePools(limits.maxIdleTotal);
246
+ };
247
+ const retainIdleAllSession = async (session, limits) => {
248
+ if (!shouldRetainIdlePool(limits)) {
249
+ await session.session.terminate();
250
+ return;
251
+ }
252
+ session.lastUsedAt = Date.now();
253
+ session.xvfb.lastUsedAt = session.lastUsedAt;
254
+ pushArrayEntry(idleAllByKey, session.key, session);
255
+ await trimIdleAllKey(session.key, limits.maxIdlePerKey);
256
+ await evictIdlePools(limits.maxIdleTotal);
257
+ };
258
+ const emptyDriverSessionPoolOptions = () => ({
259
+ allKey: void 0,
260
+ allowedMappedWindowCount: 0,
261
+ limits: nonePoolLimits(),
262
+ mode: "none",
263
+ xvfb: void 0
264
+ });
265
+ const resolveXvfbOptions = (options) => {
266
+ const screen = options.xvfbScreen ?? defaultXvfbScreen;
267
+ if (!screenPattern.test(screen)) {
268
+ throw createGtkInvalidArgumentError(
269
+ `xvfbScreen must be WIDTHxHEIGHTxDEPTH: ${screen}`
270
+ );
271
+ }
272
+ return {
273
+ pool: resolveXvfbPool(options.xvfbPool),
274
+ screen,
275
+ trayHost: options.xvfbTrayHost ?? defaultXvfbTrayHost
276
+ };
277
+ };
278
+ const resolveEffectiveDisplay = (display, xvfb) => {
279
+ if (display === "xvfb") {
280
+ return {
281
+ hostDisplay: void 0,
282
+ hostWaylandDisplay: void 0,
283
+ kind: "xvfb",
284
+ xvfb
285
+ };
286
+ }
287
+ const hostDisplay = getHostDisplayState();
288
+ if (resolveHostDisplayKind(hostDisplay) !== void 0) {
289
+ return {
290
+ hostDisplay: hostDisplay.display,
291
+ hostWaylandDisplay: hostDisplay.waylandDisplay,
292
+ kind: "host",
293
+ xvfb: void 0
294
+ };
295
+ }
296
+ return {
297
+ hostDisplay: void 0,
298
+ hostWaylandDisplay: void 0,
299
+ kind: "xvfb",
300
+ xvfb
301
+ };
302
+ };
303
+ const resolveGdkBackend = (effective) => {
304
+ if (effective.kind === "xvfb") {
305
+ return "x11";
306
+ }
307
+ if (process.env.GDK_BACKEND !== void 0) {
308
+ return void 0;
309
+ }
310
+ if (hasValue(effective.hostDisplay)) {
311
+ return "x11";
312
+ }
313
+ if (hasValue(effective.hostWaylandDisplay)) {
314
+ return "wayland";
315
+ }
316
+ return void 0;
317
+ };
318
+ const toWireEnvironment = (env) => {
319
+ const wireEnv = {};
320
+ for (const [key, value] of Object.entries(env)) {
321
+ wireEnv[key] = value ?? null;
322
+ }
323
+ return wireEnv;
324
+ };
325
+ const wireEnvironmentToGtkAppEnvironment = (env) => {
326
+ const appEnv = {};
327
+ for (const [key, value] of Object.entries(env)) {
328
+ appEnv[key] = value === null ? void 0 : value;
329
+ }
330
+ return appEnv;
331
+ };
332
+ const assertNoSessionOwnedEnvironmentOverrides = (options, effective) => {
333
+ if (effective.kind !== "xvfb" || options.env === void 0) {
334
+ return;
335
+ }
336
+ for (const key of sessionOwnedEnvironmentKeys) {
337
+ if (Object.hasOwn(options.env, key)) {
338
+ throw createGtkInvalidArgumentError(
339
+ `options.env must not override ${key} when using internal Xvfb.`
340
+ );
341
+ }
342
+ }
343
+ };
344
+ const resolveLauncherEnvironment = (options, effective) => {
345
+ assertNoSessionOwnedEnvironmentOverrides(options, effective);
346
+ return toWireEnvironment({
347
+ GDK_BACKEND: resolveGdkBackend(effective),
348
+ GSETTINGS_BACKEND: options.gsettings === null ? void 0 : options.gsettings ?? defaultGSettings,
349
+ GTK_THEME: options.theme === null ? void 0 : options.theme ?? defaultTheme,
350
+ ...options.env
351
+ });
352
+ };
353
+ const resolveDriverPath = () => {
354
+ const driverPath = resolve(
355
+ dirname(fileURLToPath(import.meta.url)),
356
+ "..",
357
+ "dist",
358
+ "gestament-launcher-driver.cjs"
359
+ );
360
+ if (!existsSync(driverPath)) {
361
+ throw createGtkOperationFailedError(
362
+ `Internal launcher driver was not found: ${driverPath}`
363
+ );
364
+ }
365
+ return driverPath;
366
+ };
367
+ const resolveXvfbPoolProbePath = () => {
368
+ const probePath = resolve(
369
+ dirname(fileURLToPath(import.meta.url)),
370
+ "..",
371
+ "dist",
372
+ "gestament-xvfb-pool-probe.cjs"
373
+ );
374
+ if (!existsSync(probePath)) {
375
+ throw createGtkOperationFailedError(
376
+ `Internal Xvfb pool probe was not found: ${probePath}`
377
+ );
378
+ }
379
+ return probePath;
380
+ };
381
+ const createDriverEnvironment = (effective, xvfb) => {
382
+ const env = { ...process.env };
383
+ delete env.AT_SPI_BUS_ADDRESS;
384
+ delete env.NO_AT_BRIDGE;
385
+ if (effective.kind === "xvfb") {
386
+ delete env.DBUS_SESSION_BUS_ADDRESS;
387
+ delete env.DISPLAY;
388
+ delete env.WAYLAND_DISPLAY;
389
+ delete env.AT_SPI_BUS_ADDRESS;
390
+ delete env.NO_AT_BRIDGE;
391
+ delete env.XAUTHORITY;
392
+ env.GDK_BACKEND = "x11";
393
+ env.GESTAMENT_XVFB_ACTIVE = "1";
394
+ env.XDG_SESSION_TYPE = "x11";
395
+ if (xvfb !== void 0) {
396
+ env.DISPLAY = xvfb.display;
397
+ }
398
+ }
399
+ return env;
400
+ };
401
+ const xvfbSocketPath = (displayNumber) => `/tmp/.X11-unix/X${displayNumber}`;
402
+ const isDisplayNumberAvailable = (displayNumber) => !leasedDisplayNumbers.has(displayNumber) && !existsSync(xvfbSocketPath(displayNumber));
403
+ const connectUnixSocket = (path, timeoutMs) => new Promise((resolveConnect, rejectConnect) => {
404
+ const socket = createConnection(path);
405
+ let settled = false;
406
+ const settle = (callback) => {
407
+ if (settled) {
408
+ return;
409
+ }
410
+ settled = true;
411
+ clearTimeout(timeout);
412
+ socket.destroy();
413
+ callback();
414
+ };
415
+ const timeout = setTimeout(() => {
416
+ settle(() => {
417
+ rejectConnect(
418
+ createGtkOperationFailedError(`Timed out connecting to ${path}.`)
419
+ );
420
+ });
421
+ }, timeoutMs);
422
+ socket.once("connect", () => {
423
+ settle(resolveConnect);
424
+ });
425
+ socket.once("error", (error) => {
426
+ settle(() => {
427
+ rejectConnect(error);
428
+ });
429
+ });
430
+ });
431
+ const waitForXvfbReady = async (displayNumber) => {
432
+ const startedAt = Date.now();
433
+ const path = xvfbSocketPath(displayNumber);
434
+ while (Date.now() - startedAt <= xvfbStartupTimeoutMs) {
435
+ if (existsSync(path)) {
436
+ try {
437
+ await connectUnixSocket(path, 250);
438
+ return;
439
+ } catch {
440
+ }
441
+ }
442
+ await delay(25);
443
+ }
444
+ throw createGtkOperationFailedError(
445
+ `Timed out waiting for Xvfb display :${displayNumber}.`
446
+ );
447
+ };
448
+ const killXvfbNow = (xvfb) => {
449
+ if (xvfb.child.exitCode === null && xvfb.child.signalCode === null) {
450
+ xvfb.child.kill("SIGTERM");
451
+ }
452
+ };
453
+ const installPoolCleanup = () => {
454
+ if (poolCleanupInstalled) {
455
+ return;
456
+ }
457
+ poolCleanupInstalled = true;
458
+ process.once("exit", () => {
459
+ for (const xvfb of directXvfbs) {
460
+ killXvfbNow(xvfb);
461
+ }
462
+ });
463
+ };
464
+ const spawnDirectXvfb = async (screen) => {
465
+ installPoolCleanup();
466
+ for (let displayNumber = firstPooledDisplayNumber; displayNumber <= lastPooledDisplayNumber; displayNumber += 1) {
467
+ if (!isDisplayNumberAvailable(displayNumber)) {
468
+ continue;
469
+ }
470
+ leasedDisplayNumbers.add(displayNumber);
471
+ const stdout = [];
472
+ const stderr = [];
473
+ const child = spawn(
474
+ "Xvfb",
475
+ [`:${displayNumber}`, "-screen", "0", screen, "-nolisten", "tcp"],
476
+ {
477
+ env: process.env,
478
+ stdio: ["ignore", "pipe", "pipe"]
479
+ }
480
+ );
481
+ child.stdout.on("data", (chunk) => {
482
+ appendOutput$1(stdout, chunk);
483
+ });
484
+ child.stderr.on("data", (chunk) => {
485
+ appendOutput$1(stderr, chunk);
486
+ });
487
+ child.unref();
488
+ unrefHandle(child.stdout);
489
+ unrefHandle(child.stderr);
490
+ try {
491
+ await Promise.race([
492
+ waitForXvfbReady(displayNumber),
493
+ new Promise((_resolve, reject) => {
494
+ child.once("error", reject);
495
+ }),
496
+ new Promise((_resolve, reject) => {
497
+ child.once("exit", (code, signal) => {
498
+ reject(
499
+ createGtkOperationFailedError(
500
+ `Xvfb exited before ready: code=${String(
501
+ code
502
+ )}, signal=${String(signal)}` + formatOutputTail(stdout, stderr)
503
+ )
504
+ );
505
+ });
506
+ })
507
+ ]);
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
+ directXvfbs.add(xvfb);
519
+ return xvfb;
520
+ } catch (error) {
521
+ killXvfbNow({
522
+ child
523
+ });
524
+ leasedDisplayNumbers.delete(displayNumber);
525
+ const message = error instanceof Error ? error.message : String(error);
526
+ if (message.includes("ENOENT")) {
527
+ throw createGtkOperationFailedError(
528
+ appendPrerequisiteInstallHint(`Failed to start Xvfb: ${message}`)
529
+ );
530
+ }
531
+ }
532
+ }
533
+ throw createGtkOperationFailedError(
534
+ `Failed to allocate a pooled Xvfb display for screen ${screen}.`
535
+ );
536
+ };
537
+ const terminateXvfb = async (xvfb) => {
538
+ removeArrayEntry(idleXvfbByKey, xvfb.key, xvfb);
539
+ directXvfbs.delete(xvfb);
540
+ if (xvfb.child.exitCode === null && xvfb.child.signalCode === null) {
541
+ xvfb.child.kill("SIGTERM");
542
+ const startedAt = Date.now();
543
+ while (xvfb.child.exitCode === null && xvfb.child.signalCode === null) {
544
+ if (Date.now() - startedAt > sessionReleaseTimeoutMs) {
545
+ xvfb.child.kill("SIGKILL");
546
+ break;
547
+ }
548
+ await delay(25);
549
+ }
550
+ }
551
+ leasedDisplayNumbers.delete(xvfb.displayNumber);
552
+ };
553
+ const leaseXvfb = async (screen) => {
554
+ for (; ; ) {
555
+ const idle = popArrayEntry(idleXvfbByKey, screen);
556
+ if (idle === void 0) {
557
+ return spawnDirectXvfb(screen);
558
+ }
559
+ if (idle.child.exitCode === null && idle.child.signalCode === null) {
560
+ idle.lastUsedAt = Date.now();
561
+ return idle;
562
+ }
563
+ await terminateXvfb(idle);
564
+ }
565
+ };
566
+ const runXvfbProbe = (xvfb) => new Promise((resolveProbe, rejectProbe) => {
567
+ const probePath = resolveXvfbPoolProbePath();
568
+ const stdout = [];
569
+ const stderr = [];
570
+ const env = {
571
+ ...process.env,
572
+ DISPLAY: xvfb.display,
573
+ GDK_BACKEND: "x11",
574
+ GESTAMENT_XVFB_ACTIVE: "1",
575
+ XDG_SESSION_TYPE: "x11"
576
+ };
577
+ delete env.AT_SPI_BUS_ADDRESS;
578
+ delete env.DBUS_SESSION_BUS_ADDRESS;
579
+ delete env.NO_AT_BRIDGE;
580
+ delete env.WAYLAND_DISPLAY;
581
+ delete env.XAUTHORITY;
582
+ const child = spawn(process.execPath, [probePath], {
583
+ env,
584
+ stdio: ["ignore", "pipe", "pipe"]
585
+ });
586
+ const timeout = setTimeout(() => {
587
+ child.kill("SIGKILL");
588
+ rejectProbe(
589
+ createGtkOperationFailedError("Timed out probing Xvfb pool.")
590
+ );
591
+ }, xvfbPoolProbeTimeoutMs);
592
+ child.stdout.on("data", (chunk) => {
593
+ appendOutput$1(stdout, chunk);
594
+ });
595
+ child.stderr.on("data", (chunk) => {
596
+ appendOutput$1(stderr, chunk);
597
+ });
598
+ child.once("error", (error) => {
599
+ clearTimeout(timeout);
600
+ rejectProbe(error);
601
+ });
602
+ child.once("exit", (code, signal) => {
603
+ clearTimeout(timeout);
604
+ if (code !== 0) {
605
+ rejectProbe(
606
+ createGtkOperationFailedError(
607
+ `Xvfb pool probe failed: code=${String(code)}, signal=${String(
608
+ signal
609
+ )}` + formatOutputTail(stdout, stderr)
610
+ )
611
+ );
612
+ return;
613
+ }
614
+ const output = stdout.join("").trim().split("\n").at(-1);
615
+ if (output === void 0 || output.length === 0) {
616
+ rejectProbe(
617
+ createGtkOperationFailedError(
618
+ "Xvfb pool probe did not return a result." + formatOutputTail(stdout, stderr)
619
+ )
620
+ );
621
+ return;
622
+ }
623
+ try {
624
+ resolveProbe(JSON.parse(output));
625
+ } catch (error) {
626
+ const message = error instanceof Error ? error.message : String(error);
627
+ rejectProbe(
628
+ createGtkOperationFailedError(
629
+ `Xvfb pool probe returned invalid JSON: ${message}` + formatOutputTail(stdout, stderr)
630
+ )
631
+ );
632
+ }
633
+ });
634
+ });
635
+ const cleanCheckXvfb = async (xvfb, allowedMappedWindowCount) => {
636
+ try {
637
+ const probe = await runXvfbProbe(xvfb);
638
+ return probe.mappedWindowCount <= allowedMappedWindowCount;
639
+ } catch {
640
+ return false;
641
+ }
642
+ };
643
+ const returnXvfbToPool = async (xvfb, limits) => {
644
+ const clean = await cleanCheckXvfb(xvfb, 0);
645
+ if (!clean) {
646
+ await terminateXvfb(xvfb);
647
+ return;
648
+ }
649
+ await retainIdleXvfb(xvfb, limits);
650
+ };
651
+ const allPoolKey = (xvfb) => `${xvfb.screen}
652
+ ${xvfb.trayHost ? "tray" : "no-tray"}`;
653
+ const spawnDriverProcess = (driverPath, socketPath, effective, xvfb) => {
654
+ const driverArgs = [
655
+ "--socket",
656
+ socketPath,
657
+ ...effective.kind === "xvfb" && effective.xvfb?.trayHost === true ? ["--with-tray-host"] : []
658
+ ];
659
+ const env = createDriverEnvironment(effective, xvfb);
660
+ const stdout = [];
661
+ const stderr = [];
662
+ const command = effective.kind === "xvfb" && xvfb === void 0 ? {
663
+ args: [
664
+ "-a",
665
+ "-s",
666
+ `-screen 0 ${effective.xvfb?.screen ?? defaultXvfbScreen}`,
667
+ "--",
668
+ "dbus-run-session",
669
+ "--",
670
+ process.execPath,
671
+ driverPath,
672
+ ...driverArgs
673
+ ],
674
+ bin: "xvfb-run"
675
+ } : effective.kind === "xvfb" ? {
676
+ args: ["--", process.execPath, driverPath, ...driverArgs],
677
+ bin: "dbus-run-session"
678
+ } : {
679
+ args: [driverPath, ...driverArgs],
680
+ bin: process.execPath
681
+ };
682
+ const child = spawn(command.bin, command.args, {
683
+ env,
684
+ stdio: ["ignore", "pipe", "pipe"]
685
+ });
686
+ child.stdout.on("data", (chunk) => {
687
+ appendOutput$1(stdout, chunk);
688
+ });
689
+ child.stderr.on("data", (chunk) => {
690
+ appendOutput$1(stderr, chunk);
691
+ });
692
+ const commandLine = [command.bin, ...command.args].join(" ");
693
+ return { child, commandLine, stderr, stdout };
694
+ };
695
+ const listenOnSocket = (server, socketPath) => new Promise((resolveListen, rejectListen) => {
696
+ const rejectFromError = (error) => {
697
+ server.removeListener("listening", resolveFromListening);
698
+ rejectListen(error);
699
+ };
700
+ const resolveFromListening = () => {
701
+ server.removeListener("error", rejectFromError);
702
+ resolveListen();
703
+ };
704
+ server.once("error", rejectFromError);
705
+ server.once("listening", resolveFromListening);
706
+ server.listen(socketPath);
707
+ });
708
+ const parseReadyMessage = (line) => {
709
+ const value = JSON.parse(line);
710
+ if (typeof value === "object" && value !== null && !Array.isArray(value) && "type" in value && value.type === "ready") {
711
+ return value;
712
+ }
713
+ return void 0;
714
+ };
715
+ const waitForDriverReady = (server, processState) => new Promise((resolveReady, rejectReady) => {
716
+ let socket;
717
+ let input = "";
718
+ let settled = false;
719
+ const cleanup = () => {
720
+ clearTimeout(timeout);
721
+ server.removeListener("connection", acceptConnection);
722
+ server.removeListener("error", rejectFromError);
723
+ processState.child.removeListener("error", rejectFromError);
724
+ processState.child.removeListener("exit", rejectFromExit);
725
+ socket?.removeListener("data", readReadyData);
726
+ socket?.removeListener("error", rejectFromError);
727
+ socket?.removeListener("close", rejectFromSocketClose);
728
+ };
729
+ const rejectOnce = (error) => {
730
+ if (settled) {
731
+ return;
732
+ }
733
+ settled = true;
734
+ cleanup();
735
+ socket?.destroy();
736
+ rejectReady(error);
737
+ };
738
+ const rejectFromError = (error) => {
739
+ rejectOnce(
740
+ createGtkOperationFailedError(
741
+ appendPrerequisiteInstallHint(
742
+ `Launcher driver failed to start: ${error.message}` + formatOutputTail(processState.stdout, processState.stderr)
743
+ )
744
+ )
745
+ );
746
+ };
747
+ const rejectFromExit = (code, signal) => {
748
+ rejectOnce(
749
+ createGtkOperationFailedError(
750
+ appendPrerequisiteInstallHint(
751
+ `Launcher driver exited before ready: code=${String(
752
+ code
753
+ )}, signal=${String(signal)}` + formatOutputTail(processState.stdout, processState.stderr)
754
+ )
755
+ )
756
+ );
757
+ };
758
+ const rejectFromSocketClose = () => {
759
+ rejectOnce(
760
+ createGtkOperationFailedError(
761
+ "Launcher driver socket closed before ready." + formatOutputTail(processState.stdout, processState.stderr)
762
+ )
763
+ );
764
+ };
765
+ const readReadyData = (chunk) => {
766
+ input += chunk.toString("utf8");
767
+ let newlineIndex = input.indexOf("\n");
768
+ while (newlineIndex >= 0) {
769
+ const line = input.slice(0, newlineIndex);
770
+ input = input.slice(newlineIndex + 1);
771
+ try {
772
+ if (parseReadyMessage(line) !== void 0) {
773
+ if (settled) {
774
+ return;
775
+ }
776
+ settled = true;
777
+ cleanup();
778
+ if (socket === void 0) {
779
+ rejectReady(
780
+ createGtkOperationFailedError(
781
+ "Launcher driver reported ready without a socket."
782
+ )
783
+ );
784
+ return;
785
+ }
786
+ resolveReady({ bufferedInput: input, socket });
787
+ return;
788
+ }
789
+ } catch (error) {
790
+ const message = error instanceof Error ? error.message : String(error);
791
+ rejectOnce(
792
+ createGtkOperationFailedError(
793
+ `Launcher driver sent invalid ready message: ${message}` + formatOutputTail(processState.stdout, processState.stderr)
794
+ )
795
+ );
796
+ return;
797
+ }
798
+ newlineIndex = input.indexOf("\n");
799
+ }
800
+ };
801
+ const acceptConnection = (acceptedSocket) => {
802
+ if (socket !== void 0) {
803
+ acceptedSocket.destroy();
804
+ return;
805
+ }
806
+ socket = acceptedSocket;
807
+ acceptedSocket.on("data", readReadyData);
808
+ acceptedSocket.once("error", rejectFromError);
809
+ acceptedSocket.once("close", rejectFromSocketClose);
810
+ };
811
+ const timeout = setTimeout(() => {
812
+ rejectOnce(
813
+ createGtkOperationFailedError(
814
+ `Timed out waiting for launcher driver: ${processState.commandLine}` + formatOutputTail(processState.stdout, processState.stderr)
815
+ )
816
+ );
817
+ }, sessionStartupTimeoutMs);
818
+ server.on("connection", acceptConnection);
819
+ server.once("error", rejectFromError);
820
+ processState.child.once("error", rejectFromError);
821
+ processState.child.once("exit", rejectFromExit);
822
+ });
823
+ const serializeRequest = (id, command, payload) => {
824
+ const deadlineMs = currentWaitDeadlineMs();
825
+ const request = deadlineMs === void 0 ? { command, id, payload } : { command, deadlineMs, id, payload };
826
+ return `${JSON.stringify(request)}
827
+ `;
828
+ };
829
+ const reconstructDriverError = (error) => {
830
+ const reconstructed = new Error(error.message);
831
+ reconstructed.name = error.name;
832
+ if (error.stack !== void 0) {
833
+ reconstructed.stack = error.stack;
834
+ }
835
+ if (error.code !== void 0) {
836
+ Object.defineProperty(reconstructed, "code", {
837
+ enumerable: true,
838
+ value: error.code
839
+ });
840
+ }
841
+ return reconstructed;
842
+ };
843
+ const decodeCapture = (capture) => ({
844
+ bounds: capture.bounds,
845
+ clipped: capture.clipped,
846
+ image: Buffer.from(capture.imageBase64, "base64"),
847
+ visibleBounds: capture.visibleBounds
848
+ });
849
+ const createDriverSession = (socket, bufferedInput, processState, tempDirectory, poolOptions) => {
850
+ const pending = /* @__PURE__ */ new Map();
851
+ let input = bufferedInput;
852
+ let nextRequestId = 1;
853
+ let closed = false;
854
+ const rejectPending = (error) => {
855
+ const entries = [...pending.values()];
856
+ pending.clear();
857
+ for (const entry of entries) {
858
+ entry.reject(error);
859
+ }
860
+ socket.unref();
861
+ };
862
+ const markClosed = () => {
863
+ if (closed) {
864
+ return;
865
+ }
866
+ closed = true;
867
+ rejectPending(createGtkAppExitedError("Launcher driver has exited."));
868
+ };
869
+ const handleResponse = (line) => {
870
+ const response = JSON.parse(line);
871
+ const entry = pending.get(response.id);
872
+ if (entry === void 0) {
873
+ return;
874
+ }
875
+ pending.delete(response.id);
876
+ if (pending.size === 0) {
877
+ socket.unref();
878
+ }
879
+ if (response.ok) {
880
+ entry.resolve(response.value);
881
+ } else {
882
+ entry.reject(
883
+ reconstructDriverError(response.error)
884
+ );
885
+ }
886
+ };
887
+ const readData = (chunk) => {
888
+ input += chunk.toString("utf8");
889
+ let newlineIndex = input.indexOf("\n");
890
+ while (newlineIndex >= 0) {
891
+ const line = input.slice(0, newlineIndex);
892
+ input = input.slice(newlineIndex + 1);
893
+ try {
894
+ handleResponse(line);
895
+ } catch (error) {
896
+ const message = error instanceof Error ? error.message : String(error);
897
+ rejectPending(
898
+ createGtkOperationFailedError(
899
+ `Launcher driver sent an invalid response: ${message}`
900
+ )
901
+ );
902
+ }
903
+ newlineIndex = input.indexOf("\n");
904
+ }
905
+ };
906
+ socket.on("data", readData);
907
+ socket.once("close", markClosed);
908
+ socket.once("error", markClosed);
909
+ processState.child.once("exit", markClosed);
910
+ processState.child.unref();
911
+ unrefHandle(processState.child.stdout);
912
+ unrefHandle(processState.child.stderr);
913
+ socket.unref();
914
+ const request = (command, payload) => {
915
+ if (closed) {
916
+ return Promise.reject(
917
+ createGtkAppExitedError("Launcher driver has exited.")
918
+ );
919
+ }
920
+ const id = nextRequestId;
921
+ nextRequestId += 1;
922
+ socket.ref();
923
+ return new Promise((resolveRequest, rejectRequest) => {
924
+ pending.set(id, {
925
+ reject: rejectRequest,
926
+ resolve: (value) => {
927
+ resolveRequest(value);
928
+ }
929
+ });
930
+ socket.write(serializeRequest(id, command, payload), (error) => {
931
+ if (error != null) {
932
+ pending.delete(id);
933
+ if (pending.size === 0) {
934
+ socket.unref();
935
+ }
936
+ rejectRequest(
937
+ createGtkOperationFailedError(
938
+ `Failed to write launcher driver request: ${error.message}`
939
+ )
940
+ );
941
+ }
942
+ });
943
+ });
944
+ };
945
+ const waitForExit = async () => {
946
+ const startedAt = Date.now();
947
+ while (processState.child.exitCode === null && processState.child.signalCode === null) {
948
+ if (Date.now() - startedAt > sessionReleaseTimeoutMs) {
949
+ processState.child.kill("SIGKILL");
950
+ break;
951
+ }
952
+ await delay(25);
953
+ }
954
+ };
955
+ const closeDriver = async () => {
956
+ if (!closed) {
957
+ try {
958
+ await request("launcher.release", null);
959
+ } catch (error) {
960
+ if (!closed) {
961
+ throw error;
962
+ }
963
+ }
964
+ }
965
+ closed = true;
966
+ socket.destroy();
967
+ await waitForExit();
968
+ rmSync(tempDirectory, { force: true, recursive: true });
969
+ };
970
+ const terminate = async () => {
971
+ try {
972
+ await closeDriver();
973
+ } finally {
974
+ if (poolOptions.xvfb !== void 0) {
975
+ await terminateXvfb(poolOptions.xvfb);
976
+ }
977
+ }
978
+ };
979
+ let session;
980
+ const release = async () => {
981
+ if (poolOptions.mode === "none") {
982
+ await closeDriver();
983
+ return;
984
+ }
985
+ if (poolOptions.mode === "xvfb") {
986
+ try {
987
+ await closeDriver();
988
+ } catch (error) {
989
+ if (poolOptions.xvfb !== void 0) {
990
+ await terminateXvfb(poolOptions.xvfb);
991
+ }
992
+ throw error;
993
+ }
994
+ if (poolOptions.xvfb !== void 0) {
995
+ await returnXvfbToPool(poolOptions.xvfb, poolOptions.limits);
996
+ }
997
+ return;
998
+ }
999
+ if (poolOptions.xvfb === void 0 || poolOptions.allKey === void 0) {
1000
+ await terminate();
1001
+ return;
1002
+ }
1003
+ let resetResult;
1004
+ try {
1005
+ resetResult = await request("launcher.reset", null);
1006
+ } catch (error) {
1007
+ await terminate().catch(() => void 0);
1008
+ throw error;
1009
+ }
1010
+ const tablesAreEmpty = resetResult.appCount === 0 && resetResult.elementCount === 0 && resetResult.imageInfoCount === 0 && resetResult.trayItemCount === 0;
1011
+ const clean = tablesAreEmpty && await cleanCheckXvfb(
1012
+ poolOptions.xvfb,
1013
+ poolOptions.allowedMappedWindowCount
1014
+ );
1015
+ if (!clean) {
1016
+ await terminate();
1017
+ return;
1018
+ }
1019
+ await retainIdleAllSession(
1020
+ {
1021
+ key: poolOptions.allKey,
1022
+ lastUsedAt: Date.now(),
1023
+ session,
1024
+ xvfb: poolOptions.xvfb
1025
+ },
1026
+ poolOptions.limits
1027
+ );
1028
+ };
1029
+ session = { release, request, terminate };
1030
+ return session;
1031
+ };
1032
+ const startFreshDriverSession = async (effective, xvfb, poolOptions) => {
1033
+ const driverPath = resolveDriverPath();
1034
+ const tempDirectory = mkdtempSync(
1035
+ join(tmpdir(), `gestament-launcher-${process.pid}-${socketCounter}-`)
1036
+ );
1037
+ socketCounter += 1;
1038
+ const socketPath = join(tempDirectory, "driver.sock");
1039
+ const server = createServer();
1040
+ let processState;
1041
+ try {
1042
+ await listenOnSocket(server, socketPath);
1043
+ server.unref();
1044
+ processState = spawnDriverProcess(driverPath, socketPath, effective, xvfb);
1045
+ const connection = await waitForDriverReady(server, processState);
1046
+ server.close();
1047
+ const resolvedPoolOptions = poolOptions.mode === "all" && xvfb !== void 0 ? {
1048
+ ...poolOptions,
1049
+ allowedMappedWindowCount: (await runXvfbProbe(xvfb)).mappedWindowCount
1050
+ } : poolOptions;
1051
+ return createDriverSession(
1052
+ connection.socket,
1053
+ connection.bufferedInput,
1054
+ processState,
1055
+ tempDirectory,
1056
+ resolvedPoolOptions
1057
+ );
1058
+ } catch (error) {
1059
+ server.close();
1060
+ if (processState !== void 0 && processState.child.exitCode === null && processState.child.signalCode === null) {
1061
+ processState.child.kill("SIGTERM");
1062
+ }
1063
+ if (xvfb !== void 0) {
1064
+ await terminateXvfb(xvfb);
1065
+ }
1066
+ rmSync(tempDirectory, { force: true, recursive: true });
1067
+ throw error;
1068
+ }
1069
+ };
1070
+ const startDriverSession = async (options) => {
1071
+ const display = resolveDisplay(options.display);
1072
+ const xvfbOptions = resolveXvfbOptions(options);
1073
+ const effective = resolveEffectiveDisplay(display, xvfbOptions);
1074
+ if (effective.kind !== "xvfb" || effective.xvfb === void 0) {
1075
+ return startFreshDriverSession(
1076
+ effective,
1077
+ void 0,
1078
+ emptyDriverSessionPoolOptions()
1079
+ );
1080
+ }
1081
+ const pool = effective.xvfb.pool;
1082
+ if (pool === void 0) {
1083
+ return startFreshDriverSession(
1084
+ effective,
1085
+ void 0,
1086
+ emptyDriverSessionPoolOptions()
1087
+ );
1088
+ }
1089
+ 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
+ });
1098
+ }
1099
+ const key = allPoolKey(effective.xvfb);
1100
+ for (; ; ) {
1101
+ const idle = popArrayEntry(idleAllByKey, key);
1102
+ if (idle === void 0) {
1103
+ break;
1104
+ }
1105
+ if (idle.xvfb.child.exitCode === null && idle.xvfb.child.signalCode === null) {
1106
+ idle.lastUsedAt = Date.now();
1107
+ idle.xvfb.lastUsedAt = idle.lastUsedAt;
1108
+ return idle.session;
1109
+ }
1110
+ await idle.session.terminate().catch(() => void 0);
1111
+ }
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
+ });
1120
+ };
1121
+ const createLaunchPayload = (options, args) => {
1122
+ const display = resolveDisplay(options.display);
1123
+ const xvfb = resolveXvfbOptions(options);
1124
+ const effective = resolveEffectiveDisplay(display, xvfb);
1125
+ return {
1126
+ appPath: options.appPath,
1127
+ args: [...options.args ?? [], ...args],
1128
+ env: resolveLauncherEnvironment(options, effective),
1129
+ timeoutMs: options.timeoutMs ?? null
1130
+ };
1131
+ };
1132
+ const createEnvironmentPayload = (options) => {
1133
+ const display = resolveDisplay(options.display);
1134
+ const xvfb = resolveXvfbOptions(options);
1135
+ const effective = resolveEffectiveDisplay(display, xvfb);
1136
+ return {
1137
+ env: resolveLauncherEnvironment(options, effective)
1138
+ };
1139
+ };
1140
+ const elementRefToProxy = (session, ref) => ref === null ? void 0 : createProxyGtkElement(session, ref);
1141
+ const trayRefToProxy = (session, ref) => ref === null ? void 0 : createProxyGtkTrayItem(session, ref);
1142
+ const createProxyGtkApp = (session, ref) => {
1143
+ let released = false;
1144
+ const assertNotReleased = () => {
1145
+ if (released) {
1146
+ throw createGtkAppExitedError("GTK application has been released.");
1147
+ }
1148
+ };
1149
+ const appRequest = (command, payload = {}) => {
1150
+ assertNotReleased();
1151
+ return session.request(command, {
1152
+ appId: ref.appId,
1153
+ ...payload
1154
+ });
1155
+ };
1156
+ const release = async () => {
1157
+ if (released) {
1158
+ return;
1159
+ }
1160
+ released = true;
1161
+ await session.request("app.release", { appId: ref.appId });
1162
+ };
1163
+ const app = {
1164
+ capture: async () => decodeCapture(await appRequest("app.capture")),
1165
+ environment: async () => wireEnvironmentToGtkAppEnvironment(
1166
+ await appRequest("app.environment")
1167
+ ),
1168
+ findById: async (id) => elementRefToProxy(
1169
+ session,
1170
+ await appRequest("app.findById", { id })
1171
+ ),
1172
+ getById: async (id) => createProxyGtkElement(
1173
+ session,
1174
+ await appRequest("app.getById", { id })
1175
+ ),
1176
+ findByPath: async (path) => elementRefToProxy(
1177
+ session,
1178
+ await appRequest("app.findByPath", { path })
1179
+ ),
1180
+ getByPath: async (path) => createProxyGtkElement(
1181
+ session,
1182
+ await appRequest("app.getByPath", { path })
1183
+ ),
1184
+ windowAt: async (index) => elementRefToProxy(
1185
+ session,
1186
+ await appRequest("app.windowAt", { index })
1187
+ ),
1188
+ getWindowCount: () => appRequest("app.getWindowCount"),
1189
+ findTrayItem: async (selector) => trayRefToProxy(
1190
+ session,
1191
+ await appRequest("app.findTrayItem", {
1192
+ selector
1193
+ })
1194
+ ),
1195
+ getTrayItem: async (selector) => createProxyGtkTrayItem(
1196
+ session,
1197
+ await appRequest("app.getTrayItem", { selector })
1198
+ ),
1199
+ trayItemAt: async (index) => trayRefToProxy(
1200
+ session,
1201
+ await appRequest("app.trayItemAt", {
1202
+ index
1203
+ })
1204
+ ),
1205
+ getTrayItemCount: () => appRequest("app.getTrayItemCount"),
1206
+ release,
1207
+ [Symbol.asyncDispose]: release
1208
+ };
1209
+ return app;
1210
+ };
1211
+ const addChildContainerProxyOperations = (session, elementId, target) => {
1212
+ target.childAt = async (index) => elementRefToProxy(
1213
+ session,
1214
+ await session.request("element.childAt", {
1215
+ elementId,
1216
+ index
1217
+ })
1218
+ );
1219
+ target.getChildCount = () => session.request("element.getChildCount", { elementId });
1220
+ };
1221
+ const addSelectableChildContainerProxyOperations = (session, elementId, target) => {
1222
+ addChildContainerProxyOperations(session, elementId, target);
1223
+ target.getSelectedChildCount = () => session.request("element.getSelectedChildCount", { elementId });
1224
+ target.selectedChildAt = async (selectedIndex) => elementRefToProxy(
1225
+ session,
1226
+ await session.request(
1227
+ "element.selectedChildAt",
1228
+ {
1229
+ elementId,
1230
+ selectedIndex
1231
+ }
1232
+ )
1233
+ );
1234
+ target.isChildSelected = (index) => session.request("element.isChildSelected", { elementId, index });
1235
+ target.selectChildAt = (index) => session.request("element.selectChildAt", { elementId, index }).then(() => void 0);
1236
+ target.deselectChildAt = (index) => session.request("element.deselectChildAt", { elementId, index }).then(() => void 0);
1237
+ target.selectAllChildren = () => session.request("element.selectAllChildren", { elementId }).then(() => void 0);
1238
+ target.clearSelection = () => session.request("element.clearSelection", { elementId }).then(() => void 0);
1239
+ };
1240
+ const addClickableProxyOperation = (session, elementId, target) => {
1241
+ target.click = () => session.request("element.click", { elementId }).then(() => void 0);
1242
+ };
1243
+ const addCheckableProxyOperations = (session, elementId, target) => {
1244
+ target.isChecked = () => session.request("element.isChecked", { elementId });
1245
+ target.toggle = () => session.request("element.toggle", { elementId }).then(() => void 0);
1246
+ };
1247
+ const addTextProxyOperation = (session, elementId, target) => {
1248
+ target.text = () => session.request("element.text", { elementId });
1249
+ };
1250
+ const addValueProxyOperations = (session, elementId, target, writable) => {
1251
+ target.value = () => session.request("element.value", { elementId });
1252
+ target.valueInfo = () => session.request("element.valueInfo", { elementId });
1253
+ if (writable) {
1254
+ target.setValue = (value) => session.request("element.setValue", { elementId, value }).then(() => void 0);
1255
+ }
1256
+ };
1257
+ const createProxyImageInfo = (session, info) => ({
1258
+ bounds: info.bounds,
1259
+ capture: async () => decodeCapture(
1260
+ await session.request("imageInfo.capture", {
1261
+ imageInfoId: info.imageInfoId
1262
+ })
1263
+ ),
1264
+ description: info.description,
1265
+ locale: info.locale,
1266
+ position: info.position,
1267
+ size: info.size
1268
+ });
1269
+ const createProxyGtkElement = (session, ref) => {
1270
+ const elementId = ref.elementId;
1271
+ const target = {
1272
+ capture: async () => decodeCapture(
1273
+ await session.request("element.capture", { elementId })
1274
+ ),
1275
+ info: () => session.request("element.info", { elementId }),
1276
+ kind: ref.kind
1277
+ };
1278
+ switch (ref.kind) {
1279
+ case "window":
1280
+ target.bounds = () => session.request("element.bounds", { elementId });
1281
+ target.resizeHints = () => session.request("window.resizeHints", {
1282
+ elementId
1283
+ });
1284
+ target.x11Info = () => session.request("window.x11Info", { elementId });
1285
+ addChildContainerProxyOperations(session, elementId, target);
1286
+ break;
1287
+ case "container":
1288
+ case "menu":
1289
+ addChildContainerProxyOperations(session, elementId, target);
1290
+ break;
1291
+ case "button":
1292
+ case "listItem":
1293
+ case "menuItem":
1294
+ addClickableProxyOperation(session, elementId, target);
1295
+ break;
1296
+ case "label":
1297
+ case "text":
1298
+ addTextProxyOperation(session, elementId, target);
1299
+ break;
1300
+ case "entry":
1301
+ addTextProxyOperation(session, elementId, target);
1302
+ target.setText = (text) => session.request("element.setText", { elementId, text }).then(() => void 0);
1303
+ break;
1304
+ case "checkbox":
1305
+ case "switch":
1306
+ case "radio":
1307
+ case "toggleButton":
1308
+ addClickableProxyOperation(session, elementId, target);
1309
+ addCheckableProxyOperations(session, elementId, target);
1310
+ break;
1311
+ case "slider":
1312
+ addValueProxyOperations(session, elementId, target, true);
1313
+ break;
1314
+ case "spinButton":
1315
+ addValueProxyOperations(session, elementId, target, true);
1316
+ target.increment = () => session.request("element.increment", { elementId }).then(() => void 0);
1317
+ target.decrement = () => session.request("element.decrement", { elementId }).then(() => void 0);
1318
+ break;
1319
+ case "progressBar":
1320
+ addValueProxyOperations(session, elementId, target, false);
1321
+ break;
1322
+ case "comboBox":
1323
+ addClickableProxyOperation(session, elementId, target);
1324
+ addSelectableChildContainerProxyOperations(session, elementId, target);
1325
+ break;
1326
+ case "list":
1327
+ addSelectableChildContainerProxyOperations(session, elementId, target);
1328
+ break;
1329
+ case "table":
1330
+ target.getRowCount = () => session.request("element.getRowCount", { elementId });
1331
+ target.getColumnCount = () => session.request("element.getColumnCount", { elementId });
1332
+ target.cellAt = async (row, column) => elementRefToProxy(
1333
+ session,
1334
+ await session.request("element.cellAt", {
1335
+ column,
1336
+ elementId,
1337
+ row
1338
+ })
1339
+ );
1340
+ target.selectedRows = () => session.request("element.selectedRows", {
1341
+ elementId
1342
+ });
1343
+ target.selectedColumns = () => session.request("element.selectedColumns", {
1344
+ elementId
1345
+ });
1346
+ target.isRowSelected = (row) => session.request("element.isRowSelected", { elementId, row });
1347
+ target.isColumnSelected = (column) => session.request("element.isColumnSelected", {
1348
+ column,
1349
+ elementId
1350
+ });
1351
+ target.isCellSelected = (row, column) => session.request("element.isCellSelected", {
1352
+ column,
1353
+ elementId,
1354
+ row
1355
+ });
1356
+ target.selectRow = (row) => session.request("element.selectRow", { elementId, row }).then(() => void 0);
1357
+ target.deselectRow = (row) => session.request("element.deselectRow", { elementId, row }).then(() => void 0);
1358
+ target.selectColumn = (column) => session.request("element.selectColumn", { column, elementId }).then(() => void 0);
1359
+ target.deselectColumn = (column) => session.request("element.deselectColumn", { column, elementId }).then(() => void 0);
1360
+ break;
1361
+ case "image":
1362
+ target.imageInfo = async () => createProxyImageInfo(
1363
+ session,
1364
+ await session.request("element.imageInfo", {
1365
+ elementId
1366
+ })
1367
+ );
1368
+ break;
1369
+ }
1370
+ return target;
1371
+ };
1372
+ const createProxyGtkTrayItem = (session, ref) => {
1373
+ const trayItemId = ref.trayItemId;
1374
+ return {
1375
+ capture: async () => decodeCapture(
1376
+ await session.request("tray.capture", { trayItemId })
1377
+ ),
1378
+ click: () => session.request("tray.click", { trayItemId }).then(() => void 0),
1379
+ element: async () => elementRefToProxy(
1380
+ session,
1381
+ await session.request("tray.element", {
1382
+ trayItemId
1383
+ })
1384
+ ),
1385
+ metadata: () => session.request("tray.metadata", { trayItemId }),
1386
+ openMenu: async () => elementRefToProxy(
1387
+ session,
1388
+ await session.request("tray.openMenu", {
1389
+ trayItemId
1390
+ })
1391
+ )
1392
+ };
1393
+ };
1394
+ const createDriverBackedGtkAppLauncher = (options) => {
1395
+ let sessionPromise;
1396
+ const ensureSession = () => {
1397
+ sessionPromise ??= startDriverSession(options);
1398
+ return sessionPromise;
1399
+ };
1400
+ const launch = async (args) => {
1401
+ const payload = createLaunchPayload(options, args ?? []);
1402
+ const session = await ensureSession();
1403
+ const appRef = await session.request(
1404
+ "launcher.launch",
1405
+ payload
1406
+ );
1407
+ return createProxyGtkApp(session, appRef);
1408
+ };
1409
+ const environment = async () => {
1410
+ const payload = createEnvironmentPayload(options);
1411
+ const session = await ensureSession();
1412
+ return wireEnvironmentToGtkAppEnvironment(
1413
+ await session.request(
1414
+ "launcher.environment",
1415
+ payload
1416
+ )
1417
+ );
1418
+ };
1419
+ const release = async () => {
1420
+ const releasingSession = sessionPromise;
1421
+ sessionPromise = void 0;
1422
+ if (releasingSession === void 0) {
1423
+ return;
1424
+ }
1425
+ const session = await releasingSession;
1426
+ await session.release();
1427
+ };
1428
+ return {
1429
+ environment,
1430
+ launch,
1431
+ release,
1432
+ [Symbol.asyncDispose]: release
1433
+ };
1434
+ };
1435
+ const assertNonNegativeIndex$1 = (name, index) => {
1436
+ if (!Number.isInteger(index) || index < 0) {
1437
+ throw createGtkInvalidArgumentError(
1438
+ `${name} must be a non-negative integer.`
1439
+ );
1440
+ }
1441
+ };
1442
+ const assertFiniteNumber = (name, value) => {
1443
+ if (!Number.isFinite(value)) {
1444
+ throw createGtkInvalidArgumentError(`${name} must be a finite number.`);
1445
+ }
1446
+ };
1447
+ const normalizeRoleName = (roleName) => roleName.trim().toLowerCase().replace(/[_-]+/g, " ").replace(/\s+/g, " ");
1448
+ const hasInterface = (info, name) => info.interfaces.some(
1449
+ (interfaceName) => interfaceName.toLowerCase() === name.toLowerCase()
1450
+ );
1451
+ const hasState = (info, name) => info.states.some((state) => state.toLowerCase() === name.toLowerCase());
1452
+ const elementInfoOverrides = /* @__PURE__ */ new WeakMap();
1453
+ const overrideElementInfo = (handle, override) => {
1454
+ elementInfoOverrides.set(handle, override);
1455
+ return handle;
1456
+ };
1457
+ const nativeInfoKind = (handle) => elementInfoOverrides.get(handle)?.kind ?? widgetKindFromInfo(nativeElementInfo(handle));
1458
+ const widgetKindFromInfo = (info) => {
1459
+ const roleName = normalizeRoleName(info.roleName);
1460
+ if (hasInterface(info, "TableCell")) {
1461
+ return "tableCell";
1462
+ }
1463
+ switch (roleName) {
1464
+ case "frame":
1465
+ case "dialog":
1466
+ case "window":
1467
+ return "window";
1468
+ case "filler":
1469
+ case "panel":
1470
+ case "scroll pane":
1471
+ case "layered pane":
1472
+ case "split pane":
1473
+ case "viewport":
1474
+ return "container";
1475
+ case "button":
1476
+ case "push button":
1477
+ case "push button menu":
1478
+ return "button";
1479
+ case "label":
1480
+ case "static":
1481
+ return "label";
1482
+ case "entry":
1483
+ case "password text":
1484
+ case "search box":
1485
+ return "entry";
1486
+ case "text":
1487
+ return hasInterface(info, "EditableText") || hasState(info, "editable") || hasState(info, "singleLine") ? "entry" : "text";
1488
+ case "paragraph":
1489
+ return "text";
1490
+ case "check box":
1491
+ return "checkbox";
1492
+ case "switch":
1493
+ return "switch";
1494
+ case "radio button":
1495
+ return "radio";
1496
+ case "toggle button":
1497
+ return hasInterface(info, "Image") || info.name.length > 0 ? "toggleButton" : "switch";
1498
+ case "slider":
1499
+ return "slider";
1500
+ case "spin button":
1501
+ return "spinButton";
1502
+ case "progress bar":
1503
+ case "level bar":
1504
+ return "progressBar";
1505
+ case "combo box":
1506
+ return "comboBox";
1507
+ case "list":
1508
+ case "list box":
1509
+ return "list";
1510
+ case "tree":
1511
+ return hasInterface(info, "Table") ? "table" : "list";
1512
+ case "list item":
1513
+ case "tree item":
1514
+ return "listItem";
1515
+ case "table":
1516
+ case "tree table":
1517
+ return "table";
1518
+ case "table cell":
1519
+ return "tableCell";
1520
+ case "image":
1521
+ case "icon":
1522
+ return "image";
1523
+ case "menu":
1524
+ case "menu bar":
1525
+ case "popup menu":
1526
+ return "menu";
1527
+ case "menu item":
1528
+ case "check menu item":
1529
+ case "radio menu item":
1530
+ case "tearoff menu item":
1531
+ return "menuItem";
1532
+ default:
1533
+ return "unknown";
1534
+ }
1535
+ };
1536
+ const toGtkElementInfo = (info) => ({
1537
+ ...info,
1538
+ kind: widgetKindFromInfo(info)
1539
+ });
1540
+ const toGtkElementInfoForHandle = (handle) => {
1541
+ const info = toGtkElementInfo(nativeElementInfo(handle));
1542
+ const override = elementInfoOverrides.get(handle);
1543
+ return override === void 0 ? info : { ...info, ...override };
1544
+ };
1545
+ const createCommonElement = (handle) => ({
1546
+ info: async () => toGtkElementInfoForHandle(handle),
1547
+ capture: async () => nativeCapture(handle)
1548
+ });
1549
+ const expectedKindLabel = (expectedKinds) => expectedKinds.join(", ");
1550
+ const assertExpectedKind = (element, expectedKinds, operation) => {
1551
+ if (!expectedKinds.includes(element.kind)) {
1552
+ throw createGtkUnsupportedInterfaceError(
1553
+ `${operation} returned ${element.kind}, expected ${expectedKindLabel(
1554
+ expectedKinds
1555
+ )}.`
1556
+ );
1557
+ }
1558
+ return element;
1559
+ };
1560
+ const createGetChildCountOperation = (handle) => async () => nativeChildCount(handle);
1561
+ const createChildAtOperation = (handle, expectedKinds) => async (index) => {
1562
+ assertNonNegativeIndex$1("index", index);
1563
+ const childHandle = nativeChildAt(handle, index);
1564
+ if (childHandle === void 0) {
1565
+ return void 0;
1566
+ }
1567
+ const child = createGtkElement(childHandle);
1568
+ return expectedKinds === void 0 ? child : assertExpectedKind(child, expectedKinds, "childAt()");
1569
+ };
1570
+ const createChildContainerOperations = (handle, expectedKinds) => ({
1571
+ childAt: createChildAtOperation(handle, expectedKinds),
1572
+ getChildCount: createGetChildCountOperation(handle)
1573
+ });
1574
+ const createSelectableChildContainerOperations = (handle, expectedKinds) => ({
1575
+ ...createChildContainerOperations(handle, expectedKinds),
1576
+ getSelectedChildCount: async () => nativeSelectedChildCount(handle),
1577
+ selectedChildAt: async (selectedIndex) => {
1578
+ assertNonNegativeIndex$1("selectedIndex", selectedIndex);
1579
+ const childHandle = nativeSelectedChildAt(handle, selectedIndex);
1580
+ if (childHandle === void 0) {
1581
+ return void 0;
1582
+ }
1583
+ const child = createGtkElement(childHandle);
1584
+ return assertExpectedKind(child, expectedKinds, "selectedChildAt()");
1585
+ },
1586
+ isChildSelected: async (index) => {
1587
+ assertNonNegativeIndex$1("index", index);
1588
+ return nativeIsChildSelected(handle, index);
1589
+ },
1590
+ selectChildAt: async (index) => {
1591
+ assertNonNegativeIndex$1("index", index);
1592
+ nativeSelectChildAt(handle, index);
1593
+ },
1594
+ deselectChildAt: async (index) => {
1595
+ assertNonNegativeIndex$1("index", index);
1596
+ nativeDeselectChildAt(handle, index);
1597
+ },
1598
+ selectAllChildren: async () => {
1599
+ nativeSelectAllChildren(handle);
1600
+ },
1601
+ clearSelection: async () => {
1602
+ nativeClearSelection(handle);
1603
+ }
1604
+ });
1605
+ const createOperationFailedForOutOfRangeIndex = (index) => createGtkOperationFailedError(`Child index is out of range: ${index}`);
1606
+ const isUnsupportedInterfaceError = (error) => error.code === "UNSUPPORTED_INTERFACE";
1607
+ const visitNativeDescendants = (handle, visitor, maxNodes) => {
1608
+ const queue = [];
1609
+ const childCount = nativeChildCount(handle);
1610
+ for (let index = 0; index < childCount; index += 1) {
1611
+ const childHandle = nativeChildAt(handle, index);
1612
+ if (childHandle !== void 0) {
1613
+ queue.push(childHandle);
1614
+ }
1615
+ }
1616
+ let visitedNodes = 0;
1617
+ while (queue.length > 0 && visitedNodes < maxNodes) {
1618
+ const currentHandle = queue.shift();
1619
+ visitedNodes += 1;
1620
+ if (visitor(currentHandle)) {
1621
+ return;
1622
+ }
1623
+ const currentChildCount = nativeChildCount(currentHandle);
1624
+ for (let index = 0; index < currentChildCount; index += 1) {
1625
+ const childHandle = nativeChildAt(currentHandle, index);
1626
+ if (childHandle !== void 0) {
1627
+ queue.push(childHandle);
1628
+ }
1629
+ }
1630
+ }
1631
+ };
1632
+ const findFirstDescendantName = (handle) => {
1633
+ let foundName;
1634
+ visitNativeDescendants(
1635
+ handle,
1636
+ (descendantHandle) => {
1637
+ const name = nativeElementInfo(descendantHandle).name;
1638
+ if (name.length === 0) {
1639
+ return false;
1640
+ }
1641
+ foundName = name;
1642
+ return true;
1643
+ },
1644
+ 64
1645
+ );
1646
+ return foundName;
1647
+ };
1648
+ const directChildKindAt = (handle, index) => {
1649
+ const childHandle = nativeChildAt(handle, index);
1650
+ return childHandle === void 0 ? void 0 : nativeInfoKind(childHandle);
1651
+ };
1652
+ const handleContainsComboItems = (handle) => {
1653
+ const childCount = nativeChildCount(handle);
1654
+ if (childCount === 0) {
1655
+ return false;
1656
+ }
1657
+ const firstChildKind = directChildKindAt(handle, 0);
1658
+ return firstChildKind === "listItem" || firstChildKind === "menuItem";
1659
+ };
1660
+ const findComboBoxItemContainerHandle = (handle) => {
1661
+ if (handleContainsComboItems(handle)) {
1662
+ return handle;
1663
+ }
1664
+ let foundHandle;
1665
+ visitNativeDescendants(
1666
+ handle,
1667
+ (descendantHandle) => {
1668
+ const kind = nativeInfoKind(descendantHandle);
1669
+ if ((kind === "list" || kind === "menu") && handleContainsComboItems(descendantHandle)) {
1670
+ foundHandle = descendantHandle;
1671
+ return true;
1672
+ }
1673
+ return false;
1674
+ },
1675
+ 128
1676
+ );
1677
+ return foundHandle;
1678
+ };
1679
+ const resolveComboBoxItemContainerHandle = (handle) => {
1680
+ const descendantItemContainer = findComboBoxItemContainerHandle(handle);
1681
+ if (descendantItemContainer !== void 0) {
1682
+ return descendantItemContainer;
1683
+ }
1684
+ const childCount = nativeChildCount(handle);
1685
+ if (childCount !== 1) {
1686
+ return handle;
1687
+ }
1688
+ const childHandle = nativeChildAt(handle, 0);
1689
+ if (childHandle === void 0) {
1690
+ return handle;
1691
+ }
1692
+ const childKind = nativeInfoKind(childHandle);
1693
+ return childKind === "menu" || childKind === "list" ? childHandle : handle;
1694
+ };
1695
+ const comboBoxClickTargetHandle = (handle) => {
1696
+ const childHandle = nativeChildAt(handle, 0);
1697
+ if (childHandle === void 0) {
1698
+ return handle;
1699
+ }
1700
+ const childKind = nativeInfoKind(childHandle);
1701
+ return childKind === "button" || childKind === "toggleButton" ? childHandle : handle;
1702
+ };
1703
+ const overrideComboBoxItemInfo = (handle) => {
1704
+ const info = nativeElementInfo(handle);
1705
+ if (info.name.length > 0) {
1706
+ return handle;
1707
+ }
1708
+ const descendantName = findFirstDescendantName(handle);
1709
+ return descendantName === void 0 ? handle : overrideElementInfo(handle, { name: descendantName });
1710
+ };
1711
+ const itemIdentityMatches = (candidate, selected) => {
1712
+ if (candidate.accessibleId.length > 0 && selected.accessibleId.length > 0) {
1713
+ return candidate.accessibleId === selected.accessibleId;
1714
+ }
1715
+ return candidate.kind === selected.kind && candidate.roleName === selected.roleName && candidate.name === selected.name && candidate.description === selected.description;
1716
+ };
1717
+ const comboBoxItemLookupAt = (handle, index) => {
1718
+ const itemContainerHandle = resolveComboBoxItemContainerHandle(handle);
1719
+ const itemHandle = nativeChildAt(itemContainerHandle, index);
1720
+ if (itemHandle === void 0) {
1721
+ throw createOperationFailedForOutOfRangeIndex(index);
1722
+ }
1723
+ const itemKind = nativeInfoKind(itemHandle);
1724
+ if (itemKind !== "listItem" && itemKind !== "menuItem") {
1725
+ throw createGtkUnsupportedInterfaceError(
1726
+ `ComboBox childAt() returned ${itemKind}, expected listItem, menuItem.`
1727
+ );
1728
+ }
1729
+ return {
1730
+ itemContainerHandle,
1731
+ itemHandle: overrideComboBoxItemInfo(itemHandle)
1732
+ };
1733
+ };
1734
+ const comboBoxItemHandleAt = (handle, index) => comboBoxItemLookupAt(handle, index).itemHandle;
1735
+ const createComboBoxOperations = (handle) => ({
1736
+ childAt: async (index) => {
1737
+ assertNonNegativeIndex$1("index", index);
1738
+ const itemContainerHandle = resolveComboBoxItemContainerHandle(handle);
1739
+ const itemHandle = nativeChildAt(itemContainerHandle, index);
1740
+ if (itemHandle === void 0) {
1741
+ return void 0;
1742
+ }
1743
+ overrideComboBoxItemInfo(itemHandle);
1744
+ return assertExpectedKind(
1745
+ createGtkElement(itemHandle),
1746
+ ["listItem", "menuItem"],
1747
+ "childAt()"
1748
+ );
1749
+ },
1750
+ getChildCount: async () => nativeChildCount(resolveComboBoxItemContainerHandle(handle)),
1751
+ getSelectedChildCount: async () => nativeSelectedChildCount(handle),
1752
+ selectedChildAt: async (selectedIndex) => {
1753
+ assertNonNegativeIndex$1("selectedIndex", selectedIndex);
1754
+ const childHandle = nativeSelectedChildAt(handle, selectedIndex);
1755
+ if (childHandle === void 0) {
1756
+ return void 0;
1757
+ }
1758
+ return assertExpectedKind(
1759
+ createGtkElement(childHandle),
1760
+ ["listItem", "menuItem"],
1761
+ "selectedChildAt()"
1762
+ );
1763
+ },
1764
+ isChildSelected: async (index) => {
1765
+ assertNonNegativeIndex$1("index", index);
1766
+ const itemHandle = comboBoxItemHandleAt(handle, index);
1767
+ const itemInfo = toGtkElementInfoForHandle(itemHandle);
1768
+ const selectedCount = nativeSelectedChildCount(handle);
1769
+ for (let selectedIndex = 0; selectedIndex < selectedCount; selectedIndex += 1) {
1770
+ const selectedHandle = nativeSelectedChildAt(handle, selectedIndex);
1771
+ if (selectedHandle === void 0) {
1772
+ continue;
1773
+ }
1774
+ const selectedInfo = toGtkElementInfo(nativeElementInfo(selectedHandle));
1775
+ if (itemIdentityMatches(itemInfo, selectedInfo)) {
1776
+ return true;
1777
+ }
1778
+ }
1779
+ return false;
1780
+ },
1781
+ selectChildAt: async (index) => {
1782
+ assertNonNegativeIndex$1("index", index);
1783
+ const { itemContainerHandle, itemHandle } = comboBoxItemLookupAt(
1784
+ handle,
1785
+ index
1786
+ );
1787
+ const expectedName = toGtkElementInfoForHandle(itemHandle).name;
1788
+ nativeClick(itemHandle);
1789
+ if (itemContainerHandle !== handle && expectedName.length > 0 && nativeElementInfo(handle).name !== expectedName) {
1790
+ throw createGtkOperationFailedError(
1791
+ "ComboBox child selection did not change the selected item."
1792
+ );
1793
+ }
1794
+ },
1795
+ deselectChildAt: async (index) => {
1796
+ assertNonNegativeIndex$1("index", index);
1797
+ comboBoxItemHandleAt(handle, index);
1798
+ nativeDeselectChildAt(handle, 0);
1799
+ },
1800
+ selectAllChildren: async () => {
1801
+ nativeSelectAllChildren(handle);
1802
+ },
1803
+ clearSelection: async () => {
1804
+ const selectedBefore = nativeSelectedChildCount(handle);
1805
+ nativeClearSelection(handle);
1806
+ if (selectedBefore > 0 && nativeSelectedChildCount(handle) > 0) {
1807
+ throw createGtkOperationFailedError(
1808
+ "ComboBox selection could not be cleared."
1809
+ );
1810
+ }
1811
+ }
1812
+ });
1813
+ const createSetTextOperation = (handle) => async (text) => {
1814
+ nativeSetText(handle, text);
1815
+ };
1816
+ const createClickOperation = (handle) => async () => {
1817
+ nativeClick(handle);
1818
+ };
1819
+ const createComboBoxClickOperation = (handle) => async () => {
1820
+ nativeClick(comboBoxClickTargetHandle(handle));
1821
+ };
1822
+ const createTextOperation = (handle) => async () => nativeText(handle);
1823
+ const createIsCheckedOperation = (handle) => async () => {
1824
+ const info = nativeElementInfo(handle);
1825
+ return hasState(info, "checked") || hasState(info, "pressed");
1826
+ };
1827
+ const createToggleOperation = (handle) => async () => {
1828
+ nativeClick(handle);
1829
+ };
1830
+ const createValueInfoOperation = (handle) => async () => nativeValueInfo(handle);
1831
+ const createImageInfoOperation = (handle) => async () => {
1832
+ const info = nativeImageInfo(handle);
1833
+ return {
1834
+ ...info,
1835
+ capture: async () => nativeCaptureBounds(info.bounds)
1836
+ };
1837
+ };
1838
+ const createBoundsOperation = (handle) => async () => nativeBounds(handle);
1839
+ const createResizeHintsOperation = (handle) => async () => nativeResizeHints(handle);
1840
+ const createX11InfoOperation = (handle) => async () => nativeX11Info(handle);
1841
+ const createValueOperation = (handle) => async () => nativeValueInfo(handle).value;
1842
+ const createSetValueOperation = (handle) => async (value) => {
1843
+ assertFiniteNumber("value", value);
1844
+ nativeSetValue(handle, value);
1845
+ };
1846
+ const assertUsableValueMetadata = (info) => {
1847
+ if (!Number.isFinite(info.value) || !Number.isFinite(info.minimum) || !Number.isFinite(info.maximum) || !Number.isFinite(info.minimumIncrement)) {
1848
+ throw createGtkOperationFailedError(
1849
+ "Accessible value metadata does not contain usable numeric values."
1850
+ );
1851
+ }
1852
+ };
1853
+ const valueStep = (info) => {
1854
+ assertUsableValueMetadata(info);
1855
+ if (info.minimumIncrement > 0) {
1856
+ return info.minimumIncrement;
1857
+ }
1858
+ const range = info.maximum - info.minimum;
1859
+ if (!Number.isFinite(range) || range <= 0) {
1860
+ throw createGtkOperationFailedError(
1861
+ "Accessible value metadata does not contain a usable increment."
1862
+ );
1863
+ }
1864
+ return Math.min(1, range);
1865
+ };
1866
+ const clampedValue = (value, minimum, maximum) => Math.min(Math.max(value, minimum), maximum);
1867
+ const createIncrementOperation = (handle) => async () => {
1868
+ const info = nativeValueInfo(handle);
1869
+ const step = valueStep(info);
1870
+ nativeSetValue(
1871
+ handle,
1872
+ clampedValue(info.value + step, info.minimum, info.maximum)
1873
+ );
1874
+ };
1875
+ const createDecrementOperation = (handle) => async () => {
1876
+ const info = nativeValueInfo(handle);
1877
+ const step = valueStep(info);
1878
+ nativeSetValue(
1879
+ handle,
1880
+ clampedValue(info.value - step, info.minimum, info.maximum)
1881
+ );
1882
+ };
1883
+ const collectDirectTableCellChildren = (rowHandle) => {
1884
+ const cells = [];
1885
+ const childCount = nativeChildCount(rowHandle);
1886
+ for (let index = 0; index < childCount; index += 1) {
1887
+ const childHandle = nativeChildAt(rowHandle, index);
1888
+ if (childHandle !== void 0 && nativeInfoKind(childHandle) === "tableCell") {
1889
+ cells.push(childHandle);
1890
+ }
1891
+ }
1892
+ return cells;
1893
+ };
1894
+ const collectFallbackTableRows = (handle) => {
1895
+ const rows = [];
1896
+ visitNativeDescendants(
1897
+ handle,
1898
+ (descendantHandle) => {
1899
+ const roleName = normalizeRoleName(
1900
+ nativeElementInfo(descendantHandle).roleName
1901
+ );
1902
+ if (roleName !== "table row") {
1903
+ return false;
1904
+ }
1905
+ const cells = collectDirectTableCellChildren(descendantHandle);
1906
+ if (cells.length > 0) {
1907
+ rows.push(
1908
+ cells.map(
1909
+ (cellHandle) => overrideElementInfo(cellHandle, { kind: "tableCell" })
1910
+ )
1911
+ );
1912
+ }
1913
+ return false;
1914
+ },
1915
+ 512
1916
+ );
1917
+ return rows;
1918
+ };
1919
+ const fallbackTableRowCount = (handle) => collectFallbackTableRows(handle).length;
1920
+ const fallbackTableColumnCount = (handle) => {
1921
+ const rows = collectFallbackTableRows(handle);
1922
+ return rows.reduce((max, row) => Math.max(max, row.length), 0);
1923
+ };
1924
+ const fallbackTableCellAt = (handle, row, column) => {
1925
+ const rows = collectFallbackTableRows(handle);
1926
+ return rows[row]?.[column];
1927
+ };
1928
+ const createTableCellAtOperation = (handle) => async (row, column) => {
1929
+ assertNonNegativeIndex$1("row", row);
1930
+ assertNonNegativeIndex$1("column", column);
1931
+ let cellHandle;
1932
+ try {
1933
+ cellHandle = nativeTableCellAt(handle, row, column);
1934
+ } catch (error) {
1935
+ if (!isUnsupportedInterfaceError(error)) {
1936
+ throw error;
1937
+ }
1938
+ cellHandle = fallbackTableCellAt(handle, row, column);
1939
+ }
1940
+ if (cellHandle === void 0) {
1941
+ return void 0;
1942
+ }
1943
+ overrideElementInfo(cellHandle, { kind: "tableCell" });
1944
+ return assertExpectedKind(
1945
+ createGtkElement(cellHandle),
1946
+ ["tableCell"],
1947
+ "cellAt()"
1948
+ );
1949
+ };
1950
+ const createTableOperations = (handle) => ({
1951
+ getRowCount: async () => {
1952
+ try {
1953
+ return nativeTableRowCount(handle);
1954
+ } catch (error) {
1955
+ if (!isUnsupportedInterfaceError(error)) {
1956
+ throw error;
1957
+ }
1958
+ return fallbackTableRowCount(handle);
1959
+ }
1960
+ },
1961
+ getColumnCount: async () => {
1962
+ try {
1963
+ return nativeTableColumnCount(handle);
1964
+ } catch (error) {
1965
+ if (!isUnsupportedInterfaceError(error)) {
1966
+ throw error;
1967
+ }
1968
+ return fallbackTableColumnCount(handle);
1969
+ }
1970
+ },
1971
+ cellAt: createTableCellAtOperation(handle),
1972
+ selectedRows: async () => nativeTableSelectedRows(handle),
1973
+ selectedColumns: async () => nativeTableSelectedColumns(handle),
1974
+ isRowSelected: async (row) => {
1975
+ assertNonNegativeIndex$1("row", row);
1976
+ return nativeTableIsRowSelected(handle, row);
1977
+ },
1978
+ isColumnSelected: async (column) => {
1979
+ assertNonNegativeIndex$1("column", column);
1980
+ return nativeTableIsColumnSelected(handle, column);
1981
+ },
1982
+ isCellSelected: async (row, column) => {
1983
+ assertNonNegativeIndex$1("row", row);
1984
+ assertNonNegativeIndex$1("column", column);
1985
+ return nativeTableIsCellSelected(handle, row, column);
1986
+ },
1987
+ selectRow: async (row) => {
1988
+ assertNonNegativeIndex$1("row", row);
1989
+ nativeTableSelectRow(handle, row);
1990
+ },
1991
+ deselectRow: async (row) => {
1992
+ assertNonNegativeIndex$1("row", row);
1993
+ nativeTableDeselectRow(handle, row);
1994
+ },
1995
+ selectColumn: async (column) => {
1996
+ assertNonNegativeIndex$1("column", column);
1997
+ nativeTableSelectColumn(handle, column);
1998
+ },
1999
+ deselectColumn: async (column) => {
2000
+ assertNonNegativeIndex$1("column", column);
2001
+ nativeTableDeselectColumn(handle, column);
2002
+ }
2003
+ });
2004
+ const createGtkElement = (handle) => {
2005
+ const initialInfo = toGtkElementInfoForHandle(handle);
2006
+ const common = createCommonElement(handle);
2007
+ switch (initialInfo.kind) {
2008
+ case "window":
2009
+ return {
2010
+ ...common,
2011
+ kind: "window",
2012
+ bounds: createBoundsOperation(handle),
2013
+ ...createChildContainerOperations(handle, void 0),
2014
+ resizeHints: createResizeHintsOperation(handle),
2015
+ x11Info: createX11InfoOperation(handle)
2016
+ };
2017
+ case "button":
2018
+ return { ...common, kind: "button", click: createClickOperation(handle) };
2019
+ case "container":
2020
+ return {
2021
+ ...common,
2022
+ kind: "container",
2023
+ ...createChildContainerOperations(handle, void 0)
2024
+ };
2025
+ case "label":
2026
+ return { ...common, kind: "label", text: createTextOperation(handle) };
2027
+ case "entry":
2028
+ return {
2029
+ ...common,
2030
+ kind: "entry",
2031
+ setText: createSetTextOperation(handle),
2032
+ text: createTextOperation(handle)
2033
+ };
2034
+ case "text":
2035
+ return { ...common, kind: "text", text: createTextOperation(handle) };
2036
+ case "checkbox":
2037
+ return {
2038
+ ...common,
2039
+ kind: "checkbox",
2040
+ click: createClickOperation(handle),
2041
+ isChecked: createIsCheckedOperation(handle),
2042
+ toggle: createToggleOperation(handle)
2043
+ };
2044
+ case "switch":
2045
+ return {
2046
+ ...common,
2047
+ kind: "switch",
2048
+ click: createClickOperation(handle),
2049
+ isChecked: createIsCheckedOperation(handle),
2050
+ toggle: createToggleOperation(handle)
2051
+ };
2052
+ case "radio":
2053
+ return {
2054
+ ...common,
2055
+ kind: "radio",
2056
+ click: createClickOperation(handle),
2057
+ isChecked: createIsCheckedOperation(handle),
2058
+ toggle: createToggleOperation(handle)
2059
+ };
2060
+ case "toggleButton":
2061
+ return {
2062
+ ...common,
2063
+ kind: "toggleButton",
2064
+ click: createClickOperation(handle),
2065
+ isChecked: createIsCheckedOperation(handle),
2066
+ toggle: createToggleOperation(handle)
2067
+ };
2068
+ case "slider":
2069
+ return {
2070
+ ...common,
2071
+ kind: "slider",
2072
+ value: createValueOperation(handle),
2073
+ valueInfo: createValueInfoOperation(handle),
2074
+ setValue: createSetValueOperation(handle)
2075
+ };
2076
+ case "spinButton":
2077
+ return {
2078
+ ...common,
2079
+ kind: "spinButton",
2080
+ value: createValueOperation(handle),
2081
+ valueInfo: createValueInfoOperation(handle),
2082
+ setValue: createSetValueOperation(handle),
2083
+ increment: createIncrementOperation(handle),
2084
+ decrement: createDecrementOperation(handle)
2085
+ };
2086
+ case "progressBar":
2087
+ return {
2088
+ ...common,
2089
+ kind: "progressBar",
2090
+ value: createValueOperation(handle),
2091
+ valueInfo: createValueInfoOperation(handle)
2092
+ };
2093
+ case "comboBox":
2094
+ return {
2095
+ ...common,
2096
+ kind: "comboBox",
2097
+ click: createComboBoxClickOperation(handle),
2098
+ ...createComboBoxOperations(handle)
2099
+ };
2100
+ case "list":
2101
+ return {
2102
+ ...common,
2103
+ kind: "list",
2104
+ ...createSelectableChildContainerOperations(
2105
+ handle,
2106
+ ["listItem"]
2107
+ )
2108
+ };
2109
+ case "listItem":
2110
+ return {
2111
+ ...common,
2112
+ kind: "listItem",
2113
+ click: createClickOperation(handle)
2114
+ };
2115
+ case "table":
2116
+ return { ...common, kind: "table", ...createTableOperations(handle) };
2117
+ case "tableCell":
2118
+ return { ...common, kind: "tableCell" };
2119
+ case "image":
2120
+ return {
2121
+ ...common,
2122
+ kind: "image",
2123
+ imageInfo: createImageInfoOperation(handle)
2124
+ };
2125
+ case "menu":
2126
+ return {
2127
+ ...common,
2128
+ kind: "menu",
2129
+ ...createChildContainerOperations(handle, [
2130
+ "menuItem"
2131
+ ])
2132
+ };
2133
+ case "menuItem":
2134
+ return {
2135
+ ...common,
2136
+ kind: "menuItem",
2137
+ click: createClickOperation(handle)
2138
+ };
2139
+ case "unknown":
2140
+ return { ...common, kind: "unknown" };
2141
+ }
2142
+ };
2143
+ const optionalString = (value) => value.length === 0 ? void 0 : value;
2144
+ const toMetadata = (item) => {
2145
+ const iconName = optionalString(item.iconName);
2146
+ const id = optionalString(item.id);
2147
+ const status = optionalString(item.status);
2148
+ const title = optionalString(item.title);
2149
+ return {
2150
+ backend: "status-notifier",
2151
+ ...iconName === void 0 ? {} : { iconName },
2152
+ ...id === void 0 ? {} : { id },
2153
+ ...status === void 0 ? {} : { status },
2154
+ ...title === void 0 ? {} : { title }
2155
+ };
2156
+ };
2157
+ const sameTrayItem = (first, second) => first.busName === second.busName && first.objectPath === second.objectPath;
2158
+ const staleTrayItemError = () => createGtkStaleElementError("Tray item is no longer registered.");
2159
+ const resolveCurrentInfo = (handle) => {
2160
+ const item = nativeTrayItems(handle.processId).find(
2161
+ (candidate) => sameTrayItem(handle, candidate)
2162
+ );
2163
+ if (item === void 0) {
2164
+ throw staleTrayItemError();
2165
+ }
2166
+ return item;
2167
+ };
2168
+ const isClickableElement = (element) => {
2169
+ switch (element.kind) {
2170
+ case "button":
2171
+ case "checkbox":
2172
+ case "switch":
2173
+ case "radio":
2174
+ case "toggleButton":
2175
+ case "comboBox":
2176
+ case "listItem":
2177
+ case "menuItem":
2178
+ return true;
2179
+ default:
2180
+ return false;
2181
+ }
2182
+ };
2183
+ const nativeTrayItemMatchesSelector = (item, selector) => {
2184
+ if ("id" in selector) {
2185
+ return item.id === selector.id;
2186
+ }
2187
+ if ("title" in selector) {
2188
+ return item.title === selector.title;
2189
+ }
2190
+ if ("busName" in selector) {
2191
+ return item.busName === selector.busName && (selector.objectPath === void 0 || item.objectPath === selector.objectPath);
2192
+ }
2193
+ return false;
2194
+ };
2195
+ const createGtkTrayItem = (processId, item) => {
2196
+ const handle = {
2197
+ busName: item.busName,
2198
+ objectPath: item.objectPath,
2199
+ processId
2200
+ };
2201
+ const trayItem = {
2202
+ metadata: async () => toMetadata(resolveCurrentInfo(handle)),
2203
+ element: async () => {
2204
+ const current = resolveCurrentInfo(handle);
2205
+ const elementHandle = nativeFindAnyById(current.accessibleId);
2206
+ return elementHandle === void 0 ? void 0 : createGtkElement(elementHandle);
2207
+ },
2208
+ capture: async () => {
2209
+ const element = await trayItem.element();
2210
+ if (element === void 0) {
2211
+ throw createGtkElementNotFoundError(
2212
+ "Tray item is registered but not visible."
2213
+ );
2214
+ }
2215
+ return element.capture();
2216
+ },
2217
+ click: async () => {
2218
+ const element = await trayItem.element();
2219
+ if (element === void 0) {
2220
+ throw createGtkElementNotFoundError(
2221
+ "Tray item is registered but not visible."
2222
+ );
2223
+ }
2224
+ if (!isClickableElement(element)) {
2225
+ throw createGtkUnsupportedInterfaceError(
2226
+ `Tray item element does not support click: ${element.kind}.`
2227
+ );
2228
+ }
2229
+ await element.click();
2230
+ },
2231
+ openMenu: async () => {
2232
+ resolveCurrentInfo(handle);
2233
+ return void 0;
2234
+ }
2235
+ };
2236
+ return trayItem;
2237
+ };
2238
+ const appendOutput = (lines, chunk) => {
2239
+ lines.push(chunk.toString("utf8"));
2240
+ if (lines.length > 40) {
2241
+ lines.splice(0, lines.length - 40);
2242
+ }
2243
+ };
2244
+ const formatProcessOutput = (state) => {
2245
+ const stdout = state.stdout.join("").trim();
2246
+ const stderr = state.stderr.join("").trim();
2247
+ if (stdout.length === 0 && stderr.length === 0) {
2248
+ return "";
2249
+ }
2250
+ return `
2251
+ stdout:
2252
+ ${stdout}
2253
+ stderr:
2254
+ ${stderr}`;
2255
+ };
2256
+ const assertProcessRunning = (state, command) => {
2257
+ if (state.exitCode !== null || state.exitSignal !== null) {
2258
+ throw createGtkAppExitedError(
2259
+ `GTK application exited before the operation completed: ${command} (code=${String(state.exitCode)}, signal=${String(state.exitSignal)})` + formatProcessOutput(state)
2260
+ );
2261
+ }
2262
+ const processId = state.process.pid;
2263
+ if (processId === void 0) {
2264
+ throw createGtkAppExitedError(
2265
+ `GTK application did not expose a process id: ${command}`
2266
+ );
2267
+ }
2268
+ return processId;
2269
+ };
2270
+ const assertNonNegativeIndex = (name, index) => {
2271
+ if (!Number.isInteger(index) || index < 0) {
2272
+ throw createGtkInvalidArgumentError(
2273
+ `${name} must be a non-negative integer.`
2274
+ );
2275
+ }
2276
+ };
2277
+ const parseElementPath = (path) => {
2278
+ const segments = path.split(/[.:;,]/u);
2279
+ const id = segments[0];
2280
+ if (id === void 0 || id.length === 0) {
2281
+ throw createGtkInvalidArgumentError(
2282
+ "path must start with an accessible id."
2283
+ );
2284
+ }
2285
+ const childIndexes = [];
2286
+ for (let index = 1; index < segments.length; index += 1) {
2287
+ const segment = segments[index];
2288
+ if (segment === void 0 || !/^\d+$/u.test(segment)) {
2289
+ throw createGtkInvalidArgumentError(
2290
+ "path child indexes must be non-negative decimal integers."
2291
+ );
2292
+ }
2293
+ const childIndex = Number(segment);
2294
+ if (!Number.isSafeInteger(childIndex)) {
2295
+ throw createGtkInvalidArgumentError(
2296
+ "path child indexes must be safe non-negative integers."
2297
+ );
2298
+ }
2299
+ childIndexes.push(childIndex);
2300
+ }
2301
+ return { id, childIndexes };
2302
+ };
2303
+ const isPathChildContainer = (element) => "childAt" in element;
2304
+ const waitForAtspiReady = async (state, command, timeoutMs, startedAt) => {
2305
+ if (state.atspiReady) {
2306
+ return assertProcessRunning(state, command);
2307
+ }
2308
+ while (Date.now() - startedAt <= timeoutMs) {
2309
+ const processId = assertProcessRunning(state, command);
2310
+ state.atspiReadiness = nativeProcessAtspiReadiness(processId);
2311
+ if (state.atspiReadiness === "ready") {
2312
+ state.atspiReady = true;
2313
+ return processId;
2314
+ }
2315
+ await delay$1(50);
2316
+ }
2317
+ assertProcessRunning(state, command);
2318
+ throw createGtkOperationFailedError(
2319
+ appendPrerequisiteInstallHint(
2320
+ `AT-SPI did not become ready for GTK application: ${command} (last readiness: ${state.atspiReadiness})` + formatProcessOutput(state)
2321
+ )
2322
+ );
2323
+ };
2324
+ const createGtkAppEnvironment = (baseEnv, overrides) => {
2325
+ const env = {
2326
+ ...baseEnv,
2327
+ GDK_BACKEND: baseEnv.GDK_BACKEND ?? "x11",
2328
+ GSETTINGS_BACKEND: baseEnv.GSETTINGS_BACKEND ?? "memory",
2329
+ GTK_THEME: baseEnv.GTK_THEME ?? "Adwaita",
2330
+ ...overrides
2331
+ };
2332
+ delete env.NO_AT_BRIDGE;
2333
+ for (const key of Object.keys(env)) {
2334
+ if (env[key] === void 0) {
2335
+ delete env[key];
2336
+ }
2337
+ }
2338
+ return env;
2339
+ };
2340
+ const launchGtkApp = (appPath, args, options) => {
2341
+ const _args = args ?? [];
2342
+ const _timeoutMs = options?.timeoutMs ?? 1e4;
2343
+ const appEnvironment = createGtkAppEnvironment(process.env, options?.env);
2344
+ const child = spawn(appPath, [..._args], {
2345
+ env: appEnvironment,
2346
+ stdio: "pipe"
2347
+ });
2348
+ const state = {
2349
+ atspiReadiness: "missing-bus-name",
2350
+ atspiReady: false,
2351
+ exitCode: null,
2352
+ exitSignal: null,
2353
+ process: child,
2354
+ stderr: [],
2355
+ stdout: []
2356
+ };
2357
+ child.stdout.on("data", (chunk) => {
2358
+ appendOutput(state.stdout, chunk);
2359
+ });
2360
+ child.stderr.on("data", (chunk) => {
2361
+ appendOutput(state.stderr, chunk);
2362
+ });
2363
+ child.on("exit", (code, signal) => {
2364
+ state.exitCode = code;
2365
+ state.exitSignal = signal;
2366
+ });
2367
+ const release = async () => {
2368
+ if (state.exitCode !== null || state.exitSignal !== null) {
2369
+ return;
2370
+ }
2371
+ child.kill("SIGTERM");
2372
+ const startedAt = Date.now();
2373
+ while (state.exitCode === null && state.exitSignal === null) {
2374
+ if (Date.now() - startedAt > 2e3) {
2375
+ child.kill("SIGKILL");
2376
+ break;
2377
+ }
2378
+ await delay$1(25);
2379
+ }
2380
+ };
2381
+ const findById = async (id) => {
2382
+ const startedAt = Date.now();
2383
+ const timeoutMs = effectiveWaitTimeoutMs(_timeoutMs);
2384
+ await waitForAtspiReady(state, appPath, timeoutMs, startedAt);
2385
+ while (Date.now() - startedAt <= timeoutMs) {
2386
+ const processId = assertProcessRunning(state, appPath);
2387
+ try {
2388
+ const handle = nativeFindById(processId, id);
2389
+ if (handle !== void 0) {
2390
+ return createGtkElement(handle);
2391
+ }
2392
+ } catch (error) {
2393
+ throw normalizeNativeError(error);
2394
+ }
2395
+ await delay$1(50);
2396
+ }
2397
+ assertProcessRunning(state, appPath);
2398
+ return void 0;
2399
+ };
2400
+ const getById = async (id) => {
2401
+ const element = await findById(id);
2402
+ if (element === void 0) {
2403
+ throw createGtkElementNotFoundError(`Accessible id was not found: ${id}`);
2404
+ }
2405
+ return element;
2406
+ };
2407
+ const findByPath = async (path) => {
2408
+ const parsedPath = parseElementPath(path);
2409
+ const startedAt = Date.now();
2410
+ const timeoutMs = effectiveWaitTimeoutMs(_timeoutMs);
2411
+ await waitForAtspiReady(state, appPath, timeoutMs, startedAt);
2412
+ while (Date.now() - startedAt <= timeoutMs) {
2413
+ const processId = assertProcessRunning(state, appPath);
2414
+ try {
2415
+ const handle = nativeFindById(processId, parsedPath.id);
2416
+ if (handle !== void 0) {
2417
+ let element = createGtkElement(handle);
2418
+ let resolved = true;
2419
+ for (const childIndex of parsedPath.childIndexes) {
2420
+ if (!isPathChildContainer(element)) {
2421
+ resolved = false;
2422
+ break;
2423
+ }
2424
+ const child2 = await element.childAt(childIndex);
2425
+ if (child2 === void 0) {
2426
+ resolved = false;
2427
+ break;
2428
+ }
2429
+ element = child2;
2430
+ }
2431
+ if (resolved) {
2432
+ return element;
2433
+ }
2434
+ }
2435
+ } catch (error) {
2436
+ throw normalizeNativeError(error);
2437
+ }
2438
+ await delay$1(50);
2439
+ }
2440
+ assertProcessRunning(state, appPath);
2441
+ return void 0;
2442
+ };
2443
+ const getByPath = async (path) => {
2444
+ const element = await findByPath(path);
2445
+ if (element === void 0) {
2446
+ throw createGtkElementNotFoundError(
2447
+ `Element path was not found: ${path}`
2448
+ );
2449
+ }
2450
+ return element;
2451
+ };
2452
+ const findTrayItem = async (selector) => {
2453
+ const startedAt = Date.now();
2454
+ const timeoutMs = effectiveWaitTimeoutMs(_timeoutMs);
2455
+ while (Date.now() - startedAt <= timeoutMs) {
2456
+ const processId = assertProcessRunning(state, appPath);
2457
+ try {
2458
+ const item = nativeTrayItems(processId).find(
2459
+ (candidate) => nativeTrayItemMatchesSelector(candidate, selector)
2460
+ );
2461
+ if (item !== void 0) {
2462
+ return createGtkTrayItem(processId, item);
2463
+ }
2464
+ } catch (error) {
2465
+ const normalizedError = normalizeNativeError(error);
2466
+ if (normalizedError.code === "OPERATION_FAILED" && normalizedError.message.includes("Timeout was reached")) {
2467
+ await delay$1(50);
2468
+ continue;
2469
+ }
2470
+ throw normalizedError;
2471
+ }
2472
+ await delay$1(50);
2473
+ }
2474
+ assertProcessRunning(state, appPath);
2475
+ return void 0;
2476
+ };
2477
+ const getTrayItem = async (selector) => {
2478
+ const trayItem = await findTrayItem(selector);
2479
+ if (trayItem === void 0) {
2480
+ throw createGtkElementNotFoundError(
2481
+ `StatusNotifier tray item was not found: ${JSON.stringify(selector)}`
2482
+ );
2483
+ }
2484
+ return trayItem;
2485
+ };
2486
+ const app = {
2487
+ capture: async () => {
2488
+ assertProcessRunning(state, appPath);
2489
+ try {
2490
+ return nativeCaptureScreen();
2491
+ } catch (error) {
2492
+ throw normalizeNativeError(error);
2493
+ }
2494
+ },
2495
+ environment: async () => {
2496
+ assertProcessRunning(state, appPath);
2497
+ return { ...appEnvironment };
2498
+ },
2499
+ findById,
2500
+ findByPath,
2501
+ getById,
2502
+ getByPath,
2503
+ windowAt: async (index) => {
2504
+ assertNonNegativeIndex("index", index);
2505
+ const startedAt = Date.now();
2506
+ const processId = await waitForAtspiReady(
2507
+ state,
2508
+ appPath,
2509
+ effectiveWaitTimeoutMs(_timeoutMs),
2510
+ startedAt
2511
+ );
2512
+ try {
2513
+ const handle = nativeWindowAt(processId, index);
2514
+ return handle === void 0 ? void 0 : createGtkElement(handle);
2515
+ } catch (error) {
2516
+ throw normalizeNativeError(error);
2517
+ }
2518
+ },
2519
+ getWindowCount: async () => {
2520
+ const startedAt = Date.now();
2521
+ const processId = await waitForAtspiReady(
2522
+ state,
2523
+ appPath,
2524
+ effectiveWaitTimeoutMs(_timeoutMs),
2525
+ startedAt
2526
+ );
2527
+ try {
2528
+ return nativeWindowCount(processId);
2529
+ } catch (error) {
2530
+ throw normalizeNativeError(error);
2531
+ }
2532
+ },
2533
+ findTrayItem,
2534
+ getTrayItem,
2535
+ trayItemAt: async (index) => {
2536
+ assertNonNegativeIndex("index", index);
2537
+ const processId = assertProcessRunning(state, appPath);
2538
+ try {
2539
+ const item = nativeTrayItems(processId)[index];
2540
+ return item === void 0 ? void 0 : createGtkTrayItem(processId, item);
2541
+ } catch (error) {
2542
+ throw normalizeNativeError(error);
2543
+ }
2544
+ },
2545
+ getTrayItemCount: async () => {
2546
+ const processId = assertProcessRunning(state, appPath);
2547
+ try {
2548
+ return nativeTrayItems(processId).length;
2549
+ } catch (error) {
2550
+ throw normalizeNativeError(error);
2551
+ }
2552
+ },
2553
+ release,
2554
+ [Symbol.asyncDispose]: release
2555
+ };
2556
+ child.on("error", (error) => {
2557
+ appendOutput(state.stderr, Buffer.from(error.message));
2558
+ });
2559
+ return Promise.resolve(app);
2560
+ };
2561
+ const createGtkAppLauncher = (options) => createDriverBackedGtkAppLauncher(options);
2562
+ export {
2563
+ createGtkAppLauncher as a,
2564
+ createGtkAppEnvironment as c,
2565
+ launchGtkApp as l
2566
+ };
2567
+ //# sourceMappingURL=launchGtkApp-Bst1BFbD.js.map