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.
- package/README.md +6 -6
- package/dist/displaySession.d.ts +18 -0
- package/dist/displaySession.d.ts.map +1 -0
- package/dist/element.d.ts +2 -2
- package/dist/element.d.ts.map +1 -1
- package/dist/errors-6gj5YuLw.cjs +36 -0
- package/dist/errors-6gj5YuLw.cjs.map +1 -0
- package/dist/errors-CCW4ATME.js +37 -0
- package/dist/errors-CCW4ATME.js.map +1 -0
- package/dist/errors.d.ts +2 -2
- package/dist/errors.d.ts.map +1 -1
- package/dist/generated/packageMetadata.d.ts +4 -4
- package/dist/gestament-config.d.ts +2 -2
- package/dist/gestament-launcher-driver.cjs +683 -0
- package/dist/gestament-launcher-driver.cjs.map +1 -0
- package/dist/gestament-launcher-driver.d.ts +13 -0
- package/dist/gestament-launcher-driver.d.ts.map +1 -0
- package/dist/gestament-launcher-driver.mjs +682 -0
- package/dist/gestament-launcher-driver.mjs.map +1 -0
- package/dist/gestament-tray-host.cjs +1 -1
- package/dist/gestament-tray-host.d.ts +2 -2
- package/dist/gestament-tray-host.mjs +1 -1
- package/dist/gestament-xvfb-pool-probe.cjs +29 -0
- package/dist/gestament-xvfb-pool-probe.cjs.map +1 -0
- package/dist/gestament-xvfb-pool-probe.d.ts +13 -0
- package/dist/gestament-xvfb-pool-probe.d.ts.map +1 -0
- package/dist/gestament-xvfb-pool-probe.mjs +28 -0
- package/dist/gestament-xvfb-pool-probe.mjs.map +1 -0
- package/dist/gestament-xvfb-worker.d.ts +2 -2
- package/dist/gestament-xvfb.cjs +15 -4
- package/dist/gestament-xvfb.cjs.map +1 -1
- package/dist/gestament-xvfb.d.ts +2 -2
- package/dist/gestament-xvfb.mjs +15 -4
- package/dist/gestament-xvfb.mjs.map +1 -1
- package/dist/index.cjs +4 -1147
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.mjs +4 -1147
- package/dist/index.mjs.map +1 -1
- package/dist/launchGtkApp-BfELuV-H.cjs +2567 -0
- package/dist/launchGtkApp-BfELuV-H.cjs.map +1 -0
- package/dist/launchGtkApp-Bst1BFbD.js +2567 -0
- package/dist/launchGtkApp-Bst1BFbD.js.map +1 -0
- package/dist/launchGtkApp.d.ts +2 -2
- package/dist/launchGtkApp.d.ts.map +1 -1
- package/dist/launcherDriverProtocol.d.ts +110 -0
- package/dist/launcherDriverProtocol.d.ts.map +1 -0
- package/dist/{native-ie_XIt1J.cjs → native-BUWDWMBB.cjs} +20 -40
- package/dist/native-BUWDWMBB.cjs.map +1 -0
- package/dist/{native-DUeVYIBs.js → native-C6MsIBNF.js} +52 -72
- package/dist/native-C6MsIBNF.js.map +1 -0
- package/dist/native.d.ts +25 -2
- package/dist/native.d.ts.map +1 -1
- package/dist/prerequisites-BuZST2Dy.cjs +15 -0
- package/dist/prerequisites-BuZST2Dy.cjs.map +1 -0
- package/dist/prerequisites-JB0SKPVd.js +16 -0
- package/dist/prerequisites-JB0SKPVd.js.map +1 -0
- package/dist/prerequisites.d.ts +23 -0
- package/dist/prerequisites.d.ts.map +1 -0
- package/dist/testing.cjs +3 -0
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.ts +4 -2
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.mjs +4 -1
- package/dist/testing.mjs.map +1 -1
- package/dist/tray.d.ts +2 -2
- package/dist/types.d.ts +158 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/wait-DV5gkXs8.js +113 -0
- package/dist/wait-DV5gkXs8.js.map +1 -0
- package/dist/wait-eOIz4nZm.cjs +112 -0
- package/dist/wait-eOIz4nZm.cjs.map +1 -0
- package/dist/wait.d.ts +67 -0
- package/dist/wait.d.ts.map +1 -0
- package/package.json +7 -7
- package/prebuilds/linux-arm/gtk3/node.napi.armv7.glibc.node +0 -0
- package/prebuilds/linux-arm/gtk4/node.napi.armv7.glibc.node +0 -0
- package/prebuilds/linux-arm64/gtk3/node.napi.glibc.node +0 -0
- package/prebuilds/linux-arm64/gtk4/node.napi.glibc.node +0 -0
- package/prebuilds/linux-ia32/gtk3/node.napi.glibc.node +0 -0
- package/prebuilds/linux-ia32/gtk4/node.napi.glibc.node +0 -0
- package/prebuilds/linux-riscv64/gtk3/node.napi.glibc.node +0 -0
- package/prebuilds/linux-riscv64/gtk4/node.napi.glibc.node +0 -0
- package/prebuilds/linux-x64/gtk3/node.napi.glibc.node +0 -0
- package/prebuilds/linux-x64/gtk4/node.napi.glibc.node +0 -0
- package/dist/native-DUeVYIBs.js.map +0 -1
- 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
|