@skylandnpm/octopus-proxy 0.1.66 → 0.1.67
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/dist/{chunk-IV5ZGRAM.js → chunk-4CKW4ICE.js} +72 -27
- package/dist/{chunk-UFLXQ4HQ.js → chunk-OTKNRBZ7.js} +310 -61
- package/dist/cli.js +2 -2
- package/dist/main.js +2 -2
- package/dist/remote-worker.js +44 -157
- package/package.json +1 -1
|
@@ -4,15 +4,16 @@ import {
|
|
|
4
4
|
PlatformDesktop,
|
|
5
5
|
REMOTE_DEFAULTS,
|
|
6
6
|
RemoteConfigSchema,
|
|
7
|
+
RemotePlatformRegistry,
|
|
7
8
|
RemoteQualitySchema,
|
|
8
9
|
SignalSchema,
|
|
9
10
|
__commonJS,
|
|
10
11
|
__toESM,
|
|
11
12
|
assertDirectSignal,
|
|
12
|
-
|
|
13
|
+
nativeDependencies,
|
|
13
14
|
remoteSessionBudget,
|
|
14
15
|
z
|
|
15
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-OTKNRBZ7.js";
|
|
16
17
|
|
|
17
18
|
// ../../node_modules/.pnpm/ajv@8.17.1/node_modules/ajv/dist/compile/codegen/code.js
|
|
18
19
|
var require_code = __commonJS({
|
|
@@ -7812,8 +7813,8 @@ async function startDeviceControl(directory, devices, connections) {
|
|
|
7812
7813
|
}
|
|
7813
7814
|
|
|
7814
7815
|
// src/daemon.ts
|
|
7815
|
-
import { mkdir as
|
|
7816
|
-
import { join as
|
|
7816
|
+
import { mkdir as mkdir10 } from "node:fs/promises";
|
|
7817
|
+
import { join as join16 } from "node:path";
|
|
7817
7818
|
|
|
7818
7819
|
// src/workspace-diff.ts
|
|
7819
7820
|
import { execFile as execFile2 } from "node:child_process";
|
|
@@ -13959,8 +13960,49 @@ var PlatformDesktopSession = class {
|
|
|
13959
13960
|
|
|
13960
13961
|
// src/remote-manager.ts
|
|
13961
13962
|
import { fork } from "node:child_process";
|
|
13962
|
-
import { randomBytes as randomBytes2, randomUUID as
|
|
13963
|
+
import { randomBytes as randomBytes2, randomUUID as randomUUID19 } from "node:crypto";
|
|
13964
|
+
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
13965
|
+
|
|
13966
|
+
// src/remote-native.ts
|
|
13967
|
+
import { chmod as chmod5, mkdir as mkdir9, readFile as readFile10, rename as rename5, rm as rm3, stat as stat10, writeFile as writeFile7 } from "node:fs/promises";
|
|
13968
|
+
import { join as join14 } from "node:path";
|
|
13963
13969
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
13970
|
+
import { createHash as createHash10, randomUUID as randomUUID18 } from "node:crypto";
|
|
13971
|
+
async function ensureRemoteHelper(stateDirectory, adapter = RemotePlatformRegistry.standard.resolve()) {
|
|
13972
|
+
await mkdir9(stateDirectory, { recursive: true, mode: 448 });
|
|
13973
|
+
const build = adapter.helperBuild();
|
|
13974
|
+
const source = fileURLToPath4(new URL(`../native/${build.source}`, import.meta.url));
|
|
13975
|
+
const target = join14(stateDirectory, "octopus-remote-desktop");
|
|
13976
|
+
const stamp = `${target}.sha256`;
|
|
13977
|
+
const fingerprint = createHash10("sha256").update(await readFile10(source)).update(JSON.stringify([adapter.context.platform, adapter.context.arch, build.flags])).digest("hex");
|
|
13978
|
+
const existing = await stat10(target).catch(() => void 0);
|
|
13979
|
+
if (existing?.isFile() && (existing.mode & 63) === 0 && await readFile10(stamp, "utf8").catch(() => "") === fingerprint) return target;
|
|
13980
|
+
const temporary = `${target}.${randomUUID18()}`, temporaryStamp = `${temporary}.sha256`;
|
|
13981
|
+
const args = build.args(source, temporary);
|
|
13982
|
+
try {
|
|
13983
|
+
await nativeDependencies.execute(build.compiler, args, { timeout: 6e4, maxBuffer: 16384 });
|
|
13984
|
+
await chmod5(temporary, 448);
|
|
13985
|
+
await writeFile7(temporaryStamp, fingerprint, { mode: 384 });
|
|
13986
|
+
await rename5(temporary, target);
|
|
13987
|
+
await rename5(temporaryStamp, stamp);
|
|
13988
|
+
return target;
|
|
13989
|
+
} catch {
|
|
13990
|
+
throw new Error(build.error);
|
|
13991
|
+
} finally {
|
|
13992
|
+
await Promise.all([rm3(temporary, { force: true }), rm3(temporaryStamp, { force: true })]);
|
|
13993
|
+
}
|
|
13994
|
+
}
|
|
13995
|
+
async function probeRemote(stateDirectory, config, adapter = RemotePlatformRegistry.standard.resolve()) {
|
|
13996
|
+
const platformError = adapter.platformError();
|
|
13997
|
+
if (platformError) throw new Error(platformError);
|
|
13998
|
+
await import("@roamhq/wrtc").catch(() => {
|
|
13999
|
+
throw new Error("WebRTC \u539F\u751F\u4F9D\u8D56\u4E0D\u53EF\u7528\uFF0C\u8BF7\u91CD\u65B0\u5B89\u88C5\u65B0\u7248 Proxy");
|
|
14000
|
+
});
|
|
14001
|
+
const helper = await ensureRemoteHelper(stateDirectory, adapter);
|
|
14002
|
+
const result = JSON.parse((await nativeDependencies.execute(helper, ["check"], { timeout: 5e3, maxBuffer: 4096 })).stdout);
|
|
14003
|
+
await adapter.checkDependencies(config);
|
|
14004
|
+
return { helper, ...result };
|
|
14005
|
+
}
|
|
13964
14006
|
|
|
13965
14007
|
// src/remote-stun.ts
|
|
13966
14008
|
import { Resolver } from "node:dns/promises";
|
|
@@ -14027,13 +14069,13 @@ async function discoverRemoteStunUrls(hubUrl, configured, resolve5 = resolvePubl
|
|
|
14027
14069
|
|
|
14028
14070
|
// src/remote-manager.ts
|
|
14029
14071
|
function spawnWorker(message) {
|
|
14030
|
-
const worker =
|
|
14072
|
+
const worker = fileURLToPath5(new URL(import.meta.url.endsWith(".ts") ? "./remote-worker.ts" : "./remote-worker.js", import.meta.url));
|
|
14031
14073
|
const child = fork(worker, [], { stdio: ["ignore", "ignore", "ignore", "ipc"], execArgv: import.meta.url.endsWith(".ts") ? ["--import", "tsx"] : [] });
|
|
14032
14074
|
child.send({ kind: "init", ...message });
|
|
14033
14075
|
return child;
|
|
14034
14076
|
}
|
|
14035
14077
|
var RemoteManager = class {
|
|
14036
|
-
constructor(host, desktop, desktopSession, send, touch, workerFactory = spawnWorker, probe = probeRemote, discoverStun = discoverRemoteStunUrls) {
|
|
14078
|
+
constructor(host, desktop, desktopSession, send, touch, workerFactory = spawnWorker, probe = probeRemote, discoverStun = discoverRemoteStunUrls, platformRegistry = RemotePlatformRegistry.standard) {
|
|
14037
14079
|
this.host = host;
|
|
14038
14080
|
this.desktop = desktop;
|
|
14039
14081
|
this.desktopSession = desktopSession;
|
|
@@ -14042,23 +14084,25 @@ var RemoteManager = class {
|
|
|
14042
14084
|
this.workerFactory = workerFactory;
|
|
14043
14085
|
this.probe = probe;
|
|
14044
14086
|
this.discoverStun = discoverStun;
|
|
14087
|
+
this.platform = platformRegistry.resolve();
|
|
14045
14088
|
this.config = RemoteConfigSchema.parse(host.desktopRemote ?? {});
|
|
14046
14089
|
}
|
|
14047
14090
|
current;
|
|
14048
14091
|
stopping = false;
|
|
14049
14092
|
config;
|
|
14093
|
+
platform;
|
|
14050
14094
|
stunUrls() {
|
|
14051
14095
|
return this.config.stunUrls ?? defaultRemoteStunUrls(this.host.hubUrl);
|
|
14052
14096
|
}
|
|
14053
14097
|
async status() {
|
|
14054
|
-
const base = { qualityModes: RemoteQualitySchema.options, platform:
|
|
14098
|
+
const base = { qualityModes: RemoteQualitySchema.options, platform: this.platform.context.platform, passwordInput: this.platform.passwordInput, enabled: this.config.enabled, busy: !!this.current, stunUrls: this.stunUrls(), budget: { ...REMOTE_DEFAULTS, totalBitrate: this.config.totalBitrate, videoMaxBitrate: this.config.videoMaxBitrate } };
|
|
14055
14099
|
if (!this.config.enabled) return { ...base, available: false, message: "\u4E3B\u673A\u5DF2\u5173\u95ED\u8FDC\u7A0B\u63A7\u5236" };
|
|
14056
14100
|
try {
|
|
14057
|
-
const permissions = await this.probe(this.host.stateDirectory, this.config);
|
|
14058
|
-
const
|
|
14059
|
-
const available =
|
|
14101
|
+
const permissions = await this.probe(this.host.stateDirectory, this.config, this.platform);
|
|
14102
|
+
const message = this.platform.unavailableReason(permissions);
|
|
14103
|
+
const available = message === void 0;
|
|
14060
14104
|
if (available) await this.desktopSession.sessionIdentity();
|
|
14061
|
-
return { ...base, available, permissions, ...!available ? { message
|
|
14105
|
+
return { ...base, available, permissions, ...!available ? { message } : {} };
|
|
14062
14106
|
} catch (error) {
|
|
14063
14107
|
return { ...base, available: false, message: error instanceof Error ? error.message : "\u8FDC\u63A7\u4F9D\u8D56\u4E0D\u53EF\u7528" };
|
|
14064
14108
|
}
|
|
@@ -14094,15 +14138,16 @@ var RemoteManager = class {
|
|
|
14094
14138
|
if (this.current) throw new Error("\u4E3B\u673A\u6B63\u5728\u88AB\u63A7\u5236");
|
|
14095
14139
|
const quality = RemoteQualitySchema.parse(payload.quality ?? "hd");
|
|
14096
14140
|
const budget = remoteSessionBudget(this.config, quality, payload.quality === void 0);
|
|
14097
|
-
const session = { remoteId:
|
|
14141
|
+
const session = { remoteId: randomUUID19(), owner, closed: false, signaling: Promise.resolve(), signals: 0, signalWindow: Date.now(), candidates: 0 };
|
|
14098
14142
|
this.current = session;
|
|
14099
14143
|
try {
|
|
14100
14144
|
const [permissions, stunUrls] = await Promise.all([
|
|
14101
|
-
this.probe(this.host.stateDirectory, this.config),
|
|
14145
|
+
this.probe(this.host.stateDirectory, this.config, this.platform),
|
|
14102
14146
|
this.discoverStun(this.host.hubUrl, this.config.stunUrls)
|
|
14103
14147
|
]);
|
|
14104
14148
|
if (!permissions.session || !permissions.capture || !permissions.input) throw new Error("\u8FDC\u63A7\u9700\u8981\u4E3B\u673A\u56FE\u5F62\u4F1A\u8BDD\u3001\u5C4F\u5E55\u5F55\u5236\u53CA\u8F85\u52A9\u529F\u80FD\u6743\u9650");
|
|
14105
|
-
|
|
14149
|
+
const unavailable = this.platform.unavailableReason(permissions);
|
|
14150
|
+
if (unavailable) throw new Error(unavailable);
|
|
14106
14151
|
const identity = await this.desktopSession.sessionIdentity();
|
|
14107
14152
|
const display = (await this.desktop.list()).find((d) => d.id === payload.displayId);
|
|
14108
14153
|
if (!display) throw new Error("\u663E\u793A\u5668\u5DF2\u65AD\u5F00\uFF0C\u8BF7\u5237\u65B0\u663E\u793A\u5668\u5217\u8868");
|
|
@@ -14173,16 +14218,16 @@ var RemoteManager = class {
|
|
|
14173
14218
|
};
|
|
14174
14219
|
|
|
14175
14220
|
// src/tool-selection.ts
|
|
14176
|
-
import { randomBytes as randomBytes3, randomUUID as
|
|
14177
|
-
import { access as access3, constants as constants6, readFile as
|
|
14221
|
+
import { randomBytes as randomBytes3, randomUUID as randomUUID20 } from "node:crypto";
|
|
14222
|
+
import { access as access3, constants as constants6, readFile as readFile11 } from "node:fs/promises";
|
|
14178
14223
|
import { platform as platform5 } from "node:os";
|
|
14179
14224
|
import { isDeepStrictEqual as isDeepStrictEqual2 } from "node:util";
|
|
14180
14225
|
|
|
14181
14226
|
// src/node-resolution.ts
|
|
14182
|
-
import { realpath as realpath7, stat as
|
|
14227
|
+
import { realpath as realpath7, stat as stat11 } from "node:fs/promises";
|
|
14183
14228
|
import { execFile as execFile7 } from "node:child_process";
|
|
14184
14229
|
import { promisify as promisify7 } from "node:util";
|
|
14185
|
-
import { join as
|
|
14230
|
+
import { join as join15 } from "node:path";
|
|
14186
14231
|
import { homedir as homedir2, platform as platform4 } from "node:os";
|
|
14187
14232
|
var run4 = promisify7(execFile7);
|
|
14188
14233
|
var VERSION_MANAGED_SEGMENTS = /* @__PURE__ */ new Set([".nvm", "nvm", ".volta", "volta", ".fnm", "fnm", ".asdf", "asdf", ".mise", "mise"]);
|
|
@@ -14226,15 +14271,15 @@ async function resolveNodePath(options = {}) {
|
|
|
14226
14271
|
return { nodePath: null, source: "exec" };
|
|
14227
14272
|
}
|
|
14228
14273
|
function toolSearchDirs(kind) {
|
|
14229
|
-
const dirs = [...describeTool(kind).searchDirs,
|
|
14274
|
+
const dirs = [...describeTool(kind).searchDirs, join15(homedir2(), ".local", "bin"), join15(homedir2(), ".opencode", "bin"), join15(homedir2(), ".codex", "bin")];
|
|
14230
14275
|
return [...new Set(dirs)];
|
|
14231
14276
|
}
|
|
14232
14277
|
async function resolveCommandOnPath(name, extraDirs = []) {
|
|
14233
14278
|
const dirs = [...(process.env.PATH ?? "").split(":"), ...extraDirs].filter(Boolean);
|
|
14234
14279
|
for (const dir of new Set(dirs)) {
|
|
14235
|
-
const full =
|
|
14280
|
+
const full = join15(dir, name);
|
|
14236
14281
|
try {
|
|
14237
|
-
const info = await
|
|
14282
|
+
const info = await stat11(full);
|
|
14238
14283
|
if (info.isFile() && info.mode & 73) return full;
|
|
14239
14284
|
} catch {
|
|
14240
14285
|
}
|
|
@@ -14317,7 +14362,7 @@ var ToolSelection = class {
|
|
|
14317
14362
|
});
|
|
14318
14363
|
}
|
|
14319
14364
|
modelCommand(projectId) {
|
|
14320
|
-
return { type: "command", id:
|
|
14365
|
+
return { type: "command", id: randomUUID20(), botId: this.config.botId, operation: "configuration.models", projectId, payload: {} };
|
|
14321
14366
|
}
|
|
14322
14367
|
profiles(raw) {
|
|
14323
14368
|
return raw.toolProfiles && typeof raw.toolProfiles === "object" && !Array.isArray(raw.toolProfiles) ? raw.toolProfiles : {};
|
|
@@ -14359,7 +14404,7 @@ var ToolSelection = class {
|
|
|
14359
14404
|
if (!isDeepStrictEqual2(JSON.parse(JSON.stringify(disk.tool)), JSON.parse(JSON.stringify(this.config.tool))) || !isDeepStrictEqual2(disk.projects, this.config.projects) || disk.botId !== this.config.botId || disk.secretKey !== this.config.secretKey) {
|
|
14360
14405
|
throw new Error("Proxy \u914D\u7F6E\u5DF2\u5728\u4E3B\u673A\u4E0A\u88AB\u4FEE\u6539\uFF1B\u8BF7\u91CD\u542F Proxy \u540E\u518D\u5207\u6362\u667A\u80FD\u4F53");
|
|
14361
14406
|
}
|
|
14362
|
-
return JSON.parse(await
|
|
14407
|
+
return JSON.parse(await readFile11(this.configPath, "utf8"));
|
|
14363
14408
|
}
|
|
14364
14409
|
async release(instance) {
|
|
14365
14410
|
instance.adapter.close();
|
|
@@ -14376,8 +14421,8 @@ var ToolSelection = class {
|
|
|
14376
14421
|
// src/daemon.ts
|
|
14377
14422
|
async function runDaemon(configPath, authenticate = verifyHostPassword) {
|
|
14378
14423
|
const config = await loadConfig(configPath);
|
|
14379
|
-
await
|
|
14380
|
-
const workspace = new Workspace(config.projects), uploads = new Uploads(workspace,
|
|
14424
|
+
await mkdir10(config.stateDirectory, { recursive: true, mode: 448 });
|
|
14425
|
+
const workspace = new Workspace(config.projects), uploads = new Uploads(workspace, join16(config.stateDirectory, "uploads")), journal = new Journal(join16(config.stateDirectory, "journal"));
|
|
14381
14426
|
let secureChat;
|
|
14382
14427
|
function emit(projectId, event, data) {
|
|
14383
14428
|
secureChat?.publish(projectId, event, data);
|
|
@@ -14399,7 +14444,7 @@ async function runDaemon(configPath, authenticate = verifyHostPassword) {
|
|
|
14399
14444
|
});
|
|
14400
14445
|
return { runtime, adapter };
|
|
14401
14446
|
});
|
|
14402
|
-
const artifacts = new Artifacts(workspace,
|
|
14447
|
+
const artifacts = new Artifacts(workspace, join16(config.stateDirectory, "artifacts"), emit);
|
|
14403
14448
|
const projectRegistry = () => new ProjectRegistry(configPath, config, tools.current.runtime, tools.current.adapter, (resetProjects) => {
|
|
14404
14449
|
for (const id of resetProjects) terminals.closeProject(id);
|
|
14405
14450
|
});
|
|
@@ -4449,66 +4449,16 @@ var DesktopManager = class {
|
|
|
4449
4449
|
}
|
|
4450
4450
|
};
|
|
4451
4451
|
|
|
4452
|
-
// src/remote-
|
|
4452
|
+
// src/remote/native-process.ts
|
|
4453
4453
|
import { execFile as execFile2, spawn } from "node:child_process";
|
|
4454
4454
|
import { promisify as promisify2 } from "node:util";
|
|
4455
|
-
import { chmod as chmod2, mkdir as mkdir2, readFile as readFile2, rename as rename2, rm as rm2, stat as stat2, writeFile as writeFile2 } from "node:fs/promises";
|
|
4456
|
-
import { join as join2 } from "node:path";
|
|
4457
|
-
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
4458
|
-
import { createHash as createHash2, randomUUID as randomUUID2 } from "node:crypto";
|
|
4459
4455
|
var exec = promisify2(execFile2);
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
}
|
|
4466
|
-
}
|
|
4467
|
-
async function ensureRemoteHelper(stateDirectory) {
|
|
4468
|
-
await mkdir2(stateDirectory, { recursive: true, mode: 448 });
|
|
4469
|
-
const mac = process.platform === "darwin";
|
|
4470
|
-
const source = fileURLToPath2(new URL(mac ? "../native/remote-macos.m" : "../native/remote-x11.c", import.meta.url));
|
|
4471
|
-
const target = join2(stateDirectory, "octopus-remote-desktop");
|
|
4472
|
-
const stamp = `${target}.sha256`;
|
|
4473
|
-
const flags = mac ? ["-O2", "-fobjc-arc", "-mmacosx-version-min=12.3", "-framework", "Foundation", "-framework", "AppKit", "-framework", "ScreenCaptureKit", "-framework", "CoreMedia", "-framework", "CoreVideo", "-framework", "ApplicationServices", "-framework", "Carbon", "-framework", "IOKit", "-framework", "VideoToolbox"] : ["-O2", "-lX11", "-lXtst"];
|
|
4474
|
-
const fingerprint = createHash2("sha256").update(await readFile2(source)).update(JSON.stringify([process.platform, process.arch, flags])).digest("hex");
|
|
4475
|
-
const existing = await stat2(target).catch(() => void 0);
|
|
4476
|
-
if (existing?.isFile() && (existing.mode & 63) === 0 && await readFile2(stamp, "utf8").catch(() => "") === fingerprint) return target;
|
|
4477
|
-
const temporary = `${target}.${randomUUID2()}`, temporaryStamp = `${temporary}.sha256`;
|
|
4478
|
-
const args = mac ? [...flags, source, "-o", temporary] : ["-O2", source, "-o", temporary, "-lX11", "-lXtst"];
|
|
4479
|
-
try {
|
|
4480
|
-
await exec(mac ? "/usr/bin/clang" : "cc", args, { timeout: 6e4, maxBuffer: 16384 });
|
|
4481
|
-
await chmod2(temporary, 448);
|
|
4482
|
-
await writeFile2(temporaryStamp, fingerprint, { mode: 384 });
|
|
4483
|
-
await rename2(temporary, target);
|
|
4484
|
-
await rename2(temporaryStamp, stamp);
|
|
4485
|
-
return target;
|
|
4486
|
-
} catch {
|
|
4487
|
-
throw new Error(mac ? "\u8FDC\u63A7\u8F85\u52A9\u7A0B\u5E8F\u7F16\u8BD1\u5931\u8D25\uFF0C\u8BF7\u5B89\u88C5 Xcode Command Line Tools" : "\u8FDC\u63A7\u8F85\u52A9\u7A0B\u5E8F\u7F16\u8BD1\u5931\u8D25\uFF0C\u8BF7\u5B89\u88C5 cc\u3001libx11-dev\u3001libxtst-dev");
|
|
4488
|
-
} finally {
|
|
4489
|
-
await Promise.all([rm2(temporary, { force: true }), rm2(temporaryStamp, { force: true })]);
|
|
4490
|
-
}
|
|
4491
|
-
}
|
|
4492
|
-
async function probeRemote(stateDirectory, config) {
|
|
4493
|
-
const platformError = remotePlatformError();
|
|
4494
|
-
if (platformError) throw new Error(platformError);
|
|
4495
|
-
await import("@roamhq/wrtc").catch(() => {
|
|
4496
|
-
throw new Error("WebRTC \u539F\u751F\u4F9D\u8D56\u4E0D\u53EF\u7528\uFF0C\u8BF7\u91CD\u65B0\u5B89\u88C5\u65B0\u7248 Proxy");
|
|
4497
|
-
});
|
|
4498
|
-
const helper = await ensureRemoteHelper(stateDirectory);
|
|
4499
|
-
const result = JSON.parse((await exec(helper, ["check"], { timeout: 5e3, maxBuffer: 4096 })).stdout);
|
|
4500
|
-
if (process.platform === "linux") {
|
|
4501
|
-
const version = await exec(config.ffmpeg, ["-version"], { timeout: 5e3, maxBuffer: 32768 }).catch(() => {
|
|
4502
|
-
throw new Error("\u8BF7\u5B89\u88C5\u652F\u6301 x11grab \u7684 LGPL FFmpeg");
|
|
4503
|
-
});
|
|
4504
|
-
assertLgplFFmpeg(version.stdout);
|
|
4505
|
-
const devices = await exec(config.ffmpeg, ["-hide_banner", "-devices"], { timeout: 5e3, maxBuffer: 32768 });
|
|
4506
|
-
if (!devices.stdout.includes("x11grab")) throw new Error("FFmpeg \u7F3A\u5C11 x11grab \u91C7\u96C6\u652F\u6301");
|
|
4507
|
-
}
|
|
4508
|
-
return { helper, ...result };
|
|
4509
|
-
}
|
|
4510
|
-
function nativeProcess(helper, displayId, video) {
|
|
4511
|
-
return spawn(helper, ["serve", displayId, String(video.width), String(video.height), String(video.fps)], { stdio: ["pipe", "pipe", "pipe", "pipe"] });
|
|
4456
|
+
var nativeDependencies = {
|
|
4457
|
+
execute: async (file, args, options) => ({ stdout: String((await exec(file, args, options)).stdout) }),
|
|
4458
|
+
spawn
|
|
4459
|
+
};
|
|
4460
|
+
function nativeProcess(helper, displayId, video, dependencies = nativeDependencies) {
|
|
4461
|
+
return dependencies.spawn(helper, ["serve", displayId, String(video.width), String(video.height), String(video.fps)], { stdio: ["pipe", "pipe", "pipe", "pipe"] });
|
|
4512
4462
|
}
|
|
4513
4463
|
function terminateNative(child) {
|
|
4514
4464
|
if (!child) return;
|
|
@@ -4519,6 +4469,307 @@ function terminateNative(child) {
|
|
|
4519
4469
|
child.once("exit", () => clearTimeout(timer));
|
|
4520
4470
|
}
|
|
4521
4471
|
|
|
4472
|
+
// src/remote/native-session.ts
|
|
4473
|
+
var NativeSession = class {
|
|
4474
|
+
constructor(options, dependencies = nativeDependencies) {
|
|
4475
|
+
this.options = options;
|
|
4476
|
+
this.dependencies = dependencies;
|
|
4477
|
+
this.video = options.video;
|
|
4478
|
+
}
|
|
4479
|
+
helper;
|
|
4480
|
+
stopped = false;
|
|
4481
|
+
video;
|
|
4482
|
+
starting;
|
|
4483
|
+
start() {
|
|
4484
|
+
return this.starting ??= this.startOnce().catch((error) => {
|
|
4485
|
+
this.stop();
|
|
4486
|
+
throw error;
|
|
4487
|
+
});
|
|
4488
|
+
}
|
|
4489
|
+
async startOnce() {
|
|
4490
|
+
if (this.stopped) return;
|
|
4491
|
+
await this.prepare();
|
|
4492
|
+
if (this.stopped) return;
|
|
4493
|
+
const child = nativeProcess(this.options.helper, this.options.display.id, this.video, this.dependencies);
|
|
4494
|
+
this.helper = child;
|
|
4495
|
+
child.on("error", () => this.fail("\u4E3B\u673A\u8F93\u5165\u8F85\u52A9\u7A0B\u5E8F\u4E0D\u53EF\u7528"));
|
|
4496
|
+
child.on("exit", () => this.fail("\u4E3B\u673A\u6743\u9650\u3001\u663E\u793A\u5668\u6216\u56FE\u5F62\u4F1A\u8BDD\u5DF2\u53D8\u5316"));
|
|
4497
|
+
child.stdin?.on("error", () => this.fail("\u4E3B\u673A\u8F93\u5165\u8FDE\u63A5\u5DF2\u5173\u95ED"));
|
|
4498
|
+
let lines = "";
|
|
4499
|
+
child.stdout?.on("data", (chunk) => {
|
|
4500
|
+
if (this.stopped) return;
|
|
4501
|
+
lines += String(chunk);
|
|
4502
|
+
if (lines.length > 15e5) {
|
|
4503
|
+
this.fail("\u4E3B\u673A\u526A\u8D34\u677F\u8D85\u8FC7\u4E0A\u9650");
|
|
4504
|
+
return;
|
|
4505
|
+
}
|
|
4506
|
+
let end;
|
|
4507
|
+
while (!this.stopped && (end = lines.indexOf("\n")) >= 0) {
|
|
4508
|
+
const line = lines.slice(0, end);
|
|
4509
|
+
lines = lines.slice(end + 1);
|
|
4510
|
+
try {
|
|
4511
|
+
const message = JSON.parse(line);
|
|
4512
|
+
if (message.kind === "error") {
|
|
4513
|
+
this.fail("\u4E3B\u673A\u6743\u9650\u3001\u663E\u793A\u5668\u6216\u56FE\u5F62\u4F1A\u8BDD\u5DF2\u53D8\u5316");
|
|
4514
|
+
return;
|
|
4515
|
+
}
|
|
4516
|
+
this.options.onMessage(message);
|
|
4517
|
+
} catch {
|
|
4518
|
+
this.fail("\u4E3B\u673A\u8F85\u52A9\u7A0B\u5E8F\u8FD4\u56DE\u65E0\u6548\u6570\u636E");
|
|
4519
|
+
}
|
|
4520
|
+
}
|
|
4521
|
+
});
|
|
4522
|
+
this.startCapture(child);
|
|
4523
|
+
}
|
|
4524
|
+
async prepare() {
|
|
4525
|
+
}
|
|
4526
|
+
stopCapture() {
|
|
4527
|
+
}
|
|
4528
|
+
fail(message) {
|
|
4529
|
+
if (this.stopped) return;
|
|
4530
|
+
this.stop();
|
|
4531
|
+
this.options.onError(message);
|
|
4532
|
+
}
|
|
4533
|
+
writeCommand(line) {
|
|
4534
|
+
if (this.stopped || !this.helper?.stdin || this.helper.stdin.destroyed) return;
|
|
4535
|
+
if (this.helper.stdin.writableLength > 2 * 1024 * 1024) {
|
|
4536
|
+
this.fail("\u4E3B\u673A\u8F93\u5165\u5904\u7406\u8D85\u65F6");
|
|
4537
|
+
return;
|
|
4538
|
+
}
|
|
4539
|
+
this.helper.stdin.write(`${line}
|
|
4540
|
+
`);
|
|
4541
|
+
}
|
|
4542
|
+
input(input) {
|
|
4543
|
+
switch (input.kind) {
|
|
4544
|
+
case "pointer":
|
|
4545
|
+
this.writeCommand(`pointer ${input.x} ${input.y}`);
|
|
4546
|
+
break;
|
|
4547
|
+
case "button":
|
|
4548
|
+
this.writeCommand(`button ${input.x} ${input.y} ${input.button} ${input.down ? 1 : 0}`);
|
|
4549
|
+
break;
|
|
4550
|
+
case "wheel":
|
|
4551
|
+
this.writeCommand(`wheel ${input.x} ${input.y} ${Math.round(input.dx)} ${Math.round(input.dy)}`);
|
|
4552
|
+
break;
|
|
4553
|
+
case "key":
|
|
4554
|
+
this.writeCommand(`key ${input.code} ${input.down ? 1 : 0}`);
|
|
4555
|
+
break;
|
|
4556
|
+
case "text":
|
|
4557
|
+
this.writeCommand(`text ${input.terminal ? 1 : 0} ${Buffer.from(input.text).toString("base64")}`);
|
|
4558
|
+
break;
|
|
4559
|
+
case "release":
|
|
4560
|
+
this.writeCommand("release");
|
|
4561
|
+
break;
|
|
4562
|
+
}
|
|
4563
|
+
}
|
|
4564
|
+
stop() {
|
|
4565
|
+
if (this.stopped) return;
|
|
4566
|
+
this.stopped = true;
|
|
4567
|
+
this.stopCapture();
|
|
4568
|
+
const helper = this.helper;
|
|
4569
|
+
this.helper = void 0;
|
|
4570
|
+
terminateNative(helper);
|
|
4571
|
+
}
|
|
4572
|
+
};
|
|
4573
|
+
|
|
4574
|
+
// src/remote/platform-adapter.ts
|
|
4575
|
+
function unsupportedPlatform(context) {
|
|
4576
|
+
return `\u8FDC\u63A7\u4E0D\u652F\u6301\u5F53\u524D\u5E73\u53F0 ${context.platform}/${context.arch}\uFF1B\u9700\u8981 macOS 12.3+ \u6216 Linux X11\uFF08x64/ARM64\uFF09`;
|
|
4577
|
+
}
|
|
4578
|
+
function missingPermissions(permissions) {
|
|
4579
|
+
if (!permissions.session || !permissions.capture || !permissions.input) return "\u8BF7\u68C0\u67E5\u4E3B\u673A\u56FE\u5F62\u4F1A\u8BDD\uFF0C\u5E76\u4E3A\u5B9E\u9645\u8FD0\u884C Proxy \u7684\u7A0B\u5E8F\u6388\u6743\u5C4F\u5E55\u5F55\u5236\u548C\u8F85\u52A9\u529F\u80FD";
|
|
4580
|
+
}
|
|
4581
|
+
|
|
4582
|
+
// src/remote/linux-x11-adapter.ts
|
|
4583
|
+
var LinuxX11RemoteAdapter = class {
|
|
4584
|
+
constructor(context, dependencies = nativeDependencies) {
|
|
4585
|
+
this.context = context;
|
|
4586
|
+
this.dependencies = dependencies;
|
|
4587
|
+
}
|
|
4588
|
+
passwordInput = false;
|
|
4589
|
+
platformError() {
|
|
4590
|
+
if (!["arm64", "x64"].includes(this.context.arch)) return unsupportedPlatform(this.context);
|
|
4591
|
+
const env = this.context.env;
|
|
4592
|
+
if (env.WAYLAND_DISPLAY || env.XDG_SESSION_TYPE === "wayland") return "\u8FDC\u63A7\u6682\u4E0D\u652F\u6301 Wayland\uFF0C\u8BF7\u4F7F\u7528 X11 \u684C\u9762\u4F1A\u8BDD";
|
|
4593
|
+
if (!env.DISPLAY) return "Proxy \u672A\u8FDE\u63A5 X11 \u56FE\u5F62\u4F1A\u8BDD\uFF1B\u8BF7\u4E3A\u5B9E\u9645\u8FD0\u884C Proxy \u7684\u670D\u52A1\u8BBE\u7F6E\u684C\u9762\u7528\u6237\u7684 DISPLAY \u548C XAUTHORITY\uFF0C\u5BB9\u5668\u4E2D\u8BF7\u5148\u542F\u52A8 Xvfb/\u684C\u9762\u670D\u52A1";
|
|
4594
|
+
}
|
|
4595
|
+
helperBuild() {
|
|
4596
|
+
return { source: "remote-x11.c", compiler: "cc", flags: ["-O2", "-lX11", "-lXtst"], args: (source, target) => ["-O2", source, "-o", target, "-lX11", "-lXtst"], error: "\u8FDC\u63A7\u8F85\u52A9\u7A0B\u5E8F\u7F16\u8BD1\u5931\u8D25\uFF0C\u8BF7\u5B89\u88C5 cc\u3001libx11-dev\u3001libxtst-dev" };
|
|
4597
|
+
}
|
|
4598
|
+
async checkDependencies(config) {
|
|
4599
|
+
const execute = this.dependencies.execute;
|
|
4600
|
+
const version = await execute(config.ffmpeg, ["-version"], { timeout: 5e3, maxBuffer: 32768 }).catch(() => {
|
|
4601
|
+
throw new Error("\u8BF7\u5B89\u88C5\u652F\u6301 x11grab \u7684 LGPL FFmpeg");
|
|
4602
|
+
});
|
|
4603
|
+
assertLgplFFmpeg(version.stdout);
|
|
4604
|
+
const devices = await execute(config.ffmpeg, ["-hide_banner", "-devices"], { timeout: 5e3, maxBuffer: 32768 });
|
|
4605
|
+
if (!devices.stdout.includes("x11grab")) throw new Error("FFmpeg \u7F3A\u5C11 x11grab \u91C7\u96C6\u652F\u6301");
|
|
4606
|
+
}
|
|
4607
|
+
unavailableReason = missingPermissions;
|
|
4608
|
+
createSession(options) {
|
|
4609
|
+
return new LinuxX11RemoteSession(options, this.context, this.dependencies);
|
|
4610
|
+
}
|
|
4611
|
+
};
|
|
4612
|
+
var LinuxX11RemoteSession = class extends NativeSession {
|
|
4613
|
+
constructor(options, context, dependencies) {
|
|
4614
|
+
super(options, dependencies);
|
|
4615
|
+
this.context = context;
|
|
4616
|
+
}
|
|
4617
|
+
capture;
|
|
4618
|
+
geometry;
|
|
4619
|
+
async prepare() {
|
|
4620
|
+
const displays = parseXrandr((await this.dependencies.execute("xrandr", ["--listactivemonitors"], { timeout: 5e3 })).stdout);
|
|
4621
|
+
if (this.stopped) return;
|
|
4622
|
+
const display = displays.find((d) => d.id === this.options.display.id);
|
|
4623
|
+
if (!display || display.width !== this.options.display.width || display.height !== this.options.display.height) throw new Error("display");
|
|
4624
|
+
this.geometry = { x: display.x, y: display.y, width: display.width, height: display.height };
|
|
4625
|
+
}
|
|
4626
|
+
startCapture(_child) {
|
|
4627
|
+
const g = this.geometry;
|
|
4628
|
+
this.writeCommand(`geometry ${g.x} ${g.y} ${g.width} ${g.height}`);
|
|
4629
|
+
this.captureFrames();
|
|
4630
|
+
}
|
|
4631
|
+
captureFrames() {
|
|
4632
|
+
this.stopCapture();
|
|
4633
|
+
if (this.stopped) return;
|
|
4634
|
+
const g = this.geometry, { width, height, fps } = this.video;
|
|
4635
|
+
const child = this.dependencies.spawn(this.options.config.ffmpeg, ["-nostdin", "-hide_banner", "-loglevel", "error", "-f", "x11grab", "-framerate", String(fps), "-video_size", `${g.width}x${g.height}`, "-grab_x", String(g.x), "-grab_y", String(g.y), "-draw_mouse", "1", "-i", this.context.env.DISPLAY, "-vf", `scale=${width}:${height}`, "-pix_fmt", "yuv420p", "-f", "rawvideo", "pipe:1"], { stdio: ["ignore", "pipe", "ignore"], env: this.context.env });
|
|
4636
|
+
this.capture = child;
|
|
4637
|
+
const current = () => !this.stopped && this.capture === child;
|
|
4638
|
+
let buffer = Buffer.alloc(0);
|
|
4639
|
+
const length = width * height * 1.5;
|
|
4640
|
+
child.stdout.on("data", (chunk) => {
|
|
4641
|
+
if (!current()) return;
|
|
4642
|
+
buffer = Buffer.concat([buffer, chunk]);
|
|
4643
|
+
if (buffer.length > length * 4) {
|
|
4644
|
+
this.fail("\u4E3B\u673A\u91C7\u96C6\u7F13\u51B2\u533A\u8D85\u9650");
|
|
4645
|
+
return;
|
|
4646
|
+
}
|
|
4647
|
+
if (buffer.length >= length) {
|
|
4648
|
+
const count = Math.floor(buffer.length / length), frame = buffer.subarray((count - 1) * length, count * length);
|
|
4649
|
+
buffer = buffer.subarray(count * length);
|
|
4650
|
+
this.options.onFrame({ width, height, data: new Uint8Array(frame) });
|
|
4651
|
+
}
|
|
4652
|
+
});
|
|
4653
|
+
child.on("error", () => {
|
|
4654
|
+
if (current()) this.fail("FFmpeg \u91C7\u96C6\u5931\u8D25");
|
|
4655
|
+
});
|
|
4656
|
+
child.on("exit", () => {
|
|
4657
|
+
if (current()) this.fail("\u663E\u793A\u5668\u5DF2\u53D8\u5316\u6216 FFmpeg \u91C7\u96C6\u5931\u8D25");
|
|
4658
|
+
});
|
|
4659
|
+
}
|
|
4660
|
+
setVideo(video) {
|
|
4661
|
+
this.video = video;
|
|
4662
|
+
if (this.helper && !this.stopped) this.captureFrames();
|
|
4663
|
+
}
|
|
4664
|
+
stopCapture() {
|
|
4665
|
+
const child = this.capture;
|
|
4666
|
+
this.capture = void 0;
|
|
4667
|
+
terminateNative(child);
|
|
4668
|
+
}
|
|
4669
|
+
};
|
|
4670
|
+
|
|
4671
|
+
// src/remote/macos-adapter.ts
|
|
4672
|
+
var MAC_LOCK_CAPTURE_ERROR = "\u9501\u5C4F\u8FDC\u63A7\u9700\u8981 macOS 14 \u53CA\u4EE5\u4E0A\u53CA\u65B0\u7248 Xcode Command Line Tools\uFF1B\u5F53\u524D\u4E3B\u673A\u8BF7\u5148\u672C\u5730\u89E3\u9501";
|
|
4673
|
+
var MacOSRemoteAdapter = class {
|
|
4674
|
+
constructor(context, dependencies = nativeDependencies) {
|
|
4675
|
+
this.context = context;
|
|
4676
|
+
this.dependencies = dependencies;
|
|
4677
|
+
}
|
|
4678
|
+
passwordInput = true;
|
|
4679
|
+
platformError() {
|
|
4680
|
+
if (!["arm64", "x64"].includes(this.context.arch)) return unsupportedPlatform(this.context);
|
|
4681
|
+
}
|
|
4682
|
+
helperBuild() {
|
|
4683
|
+
const flags = ["-O2", "-fobjc-arc", "-mmacosx-version-min=12.3", "-framework", "Foundation", "-framework", "AppKit", "-framework", "ScreenCaptureKit", "-framework", "CoreMedia", "-framework", "CoreVideo", "-framework", "ApplicationServices", "-framework", "Carbon", "-framework", "IOKit", "-framework", "VideoToolbox"];
|
|
4684
|
+
return { source: "remote-macos.m", compiler: "/usr/bin/clang", flags, args: (source, target) => [...flags, source, "-o", target], error: "\u8FDC\u63A7\u8F85\u52A9\u7A0B\u5E8F\u7F16\u8BD1\u5931\u8D25\uFF0C\u8BF7\u5B89\u88C5 Xcode Command Line Tools" };
|
|
4685
|
+
}
|
|
4686
|
+
async checkDependencies(_config) {
|
|
4687
|
+
}
|
|
4688
|
+
unavailableReason(permissions) {
|
|
4689
|
+
if (permissions.locked === true && permissions.lockCapture === false) return MAC_LOCK_CAPTURE_ERROR;
|
|
4690
|
+
return missingPermissions(permissions);
|
|
4691
|
+
}
|
|
4692
|
+
createSession(options) {
|
|
4693
|
+
return new MacOSRemoteSession(options, this.dependencies);
|
|
4694
|
+
}
|
|
4695
|
+
};
|
|
4696
|
+
var MacOSRemoteSession = class extends NativeSession {
|
|
4697
|
+
startCapture(child) {
|
|
4698
|
+
const stream = child.stdio[3];
|
|
4699
|
+
let buffer = Buffer.alloc(0);
|
|
4700
|
+
stream.on("data", (chunk) => {
|
|
4701
|
+
if (this.stopped) return;
|
|
4702
|
+
buffer = Buffer.concat([buffer, chunk]);
|
|
4703
|
+
if (buffer.length > REMOTE_DEFAULTS.maxWidth * REMOTE_DEFAULTS.maxHeight * 3 + 24) {
|
|
4704
|
+
this.fail("\u8FDC\u63A7\u753B\u9762\u7F13\u51B2\u533A\u8D85\u9650");
|
|
4705
|
+
return;
|
|
4706
|
+
}
|
|
4707
|
+
let latest;
|
|
4708
|
+
while (buffer.length >= 12) {
|
|
4709
|
+
const width = buffer.readUInt32BE(0), height = buffer.readUInt32BE(4), length = buffer.readUInt32BE(8);
|
|
4710
|
+
if (!width || !height || width > REMOTE_DEFAULTS.maxWidth || height > REMOTE_DEFAULTS.maxHeight || width % 2 || height % 2 || length !== width * height * 1.5) {
|
|
4711
|
+
this.fail("\u4E3B\u673A\u753B\u9762\u683C\u5F0F\u65E0\u6548");
|
|
4712
|
+
return;
|
|
4713
|
+
}
|
|
4714
|
+
if (buffer.length < length + 12) break;
|
|
4715
|
+
latest = { width, height, data: new Uint8Array(buffer.subarray(12, 12 + length)) };
|
|
4716
|
+
buffer = buffer.subarray(12 + length);
|
|
4717
|
+
}
|
|
4718
|
+
if (latest) this.options.onFrame(latest);
|
|
4719
|
+
});
|
|
4720
|
+
}
|
|
4721
|
+
setVideo(video) {
|
|
4722
|
+
this.video = video;
|
|
4723
|
+
this.writeCommand(`video ${video.width} ${video.height} ${video.fps}`);
|
|
4724
|
+
}
|
|
4725
|
+
input(input) {
|
|
4726
|
+
if (input.kind === "password") this.writeCommand(`password ${Buffer.from(input.text).toString("base64")}`);
|
|
4727
|
+
else super.input(input);
|
|
4728
|
+
}
|
|
4729
|
+
};
|
|
4730
|
+
|
|
4731
|
+
// src/remote/unsupported-adapter.ts
|
|
4732
|
+
var UnsupportedRemoteAdapter = class {
|
|
4733
|
+
constructor(context) {
|
|
4734
|
+
this.context = context;
|
|
4735
|
+
}
|
|
4736
|
+
passwordInput = false;
|
|
4737
|
+
platformError() {
|
|
4738
|
+
return unsupportedPlatform(this.context);
|
|
4739
|
+
}
|
|
4740
|
+
helperBuild() {
|
|
4741
|
+
throw new Error(this.platformError());
|
|
4742
|
+
}
|
|
4743
|
+
async checkDependencies(_config) {
|
|
4744
|
+
throw new Error(this.platformError());
|
|
4745
|
+
}
|
|
4746
|
+
unavailableReason(_permissions) {
|
|
4747
|
+
return this.platformError();
|
|
4748
|
+
}
|
|
4749
|
+
createSession(_options) {
|
|
4750
|
+
throw new Error(this.platformError());
|
|
4751
|
+
}
|
|
4752
|
+
};
|
|
4753
|
+
|
|
4754
|
+
// src/remote/platform-registry.ts
|
|
4755
|
+
var RemotePlatformRegistry = class _RemotePlatformRegistry {
|
|
4756
|
+
static standard = new _RemotePlatformRegistry({
|
|
4757
|
+
darwin: (context) => new MacOSRemoteAdapter(context),
|
|
4758
|
+
linux: (context) => new LinuxX11RemoteAdapter(context)
|
|
4759
|
+
});
|
|
4760
|
+
factories;
|
|
4761
|
+
constructor(factories) {
|
|
4762
|
+
this.factories = Object.freeze({ ...factories });
|
|
4763
|
+
}
|
|
4764
|
+
withAdapter(platform, factory) {
|
|
4765
|
+
return new _RemotePlatformRegistry({ ...this.factories, [platform]: factory });
|
|
4766
|
+
}
|
|
4767
|
+
resolve(context = { platform: process.platform, arch: process.arch, env: process.env }) {
|
|
4768
|
+
const factory = Object.hasOwn(this.factories, context.platform) ? this.factories[context.platform] : void 0;
|
|
4769
|
+
return factory?.(context) ?? new UnsupportedRemoteAdapter(context);
|
|
4770
|
+
}
|
|
4771
|
+
};
|
|
4772
|
+
|
|
4522
4773
|
export {
|
|
4523
4774
|
__commonJS,
|
|
4524
4775
|
__toESM,
|
|
@@ -4539,10 +4790,8 @@ export {
|
|
|
4539
4790
|
DESKTOP_PERMISSION_GUIDANCE,
|
|
4540
4791
|
requestDesktopPermission,
|
|
4541
4792
|
checkDesktop,
|
|
4542
|
-
parseXrandr,
|
|
4543
4793
|
PlatformDesktop,
|
|
4544
4794
|
DesktopManager,
|
|
4545
|
-
|
|
4546
|
-
|
|
4547
|
-
terminateNative
|
|
4795
|
+
nativeDependencies,
|
|
4796
|
+
RemotePlatformRegistry
|
|
4548
4797
|
};
|
package/dist/cli.js
CHANGED
|
@@ -35,13 +35,13 @@ import {
|
|
|
35
35
|
supportedPlatform,
|
|
36
36
|
systemdUnitPath,
|
|
37
37
|
toolSearchDirs
|
|
38
|
-
} from "./chunk-
|
|
38
|
+
} from "./chunk-4CKW4ICE.js";
|
|
39
39
|
import {
|
|
40
40
|
DESKTOP_PERMISSION_GUIDANCE,
|
|
41
41
|
PlatformDesktop,
|
|
42
42
|
checkDesktop,
|
|
43
43
|
requestDesktopPermission
|
|
44
|
-
} from "./chunk-
|
|
44
|
+
} from "./chunk-OTKNRBZ7.js";
|
|
45
45
|
|
|
46
46
|
// src/cli.ts
|
|
47
47
|
import { spawn as spawn2 } from "node:child_process";
|
package/dist/main.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
import {
|
|
3
3
|
ConfigError,
|
|
4
4
|
runDaemon
|
|
5
|
-
} from "./chunk-
|
|
6
|
-
import "./chunk-
|
|
5
|
+
} from "./chunk-4CKW4ICE.js";
|
|
6
|
+
import "./chunk-OTKNRBZ7.js";
|
|
7
7
|
|
|
8
8
|
// src/main.ts
|
|
9
9
|
runDaemon(process.env.OCTOPUS_PROXY_CONFIG ?? "proxy.json").catch((error) => {
|
package/dist/remote-worker.js
CHANGED
|
@@ -1,25 +1,20 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
InputSchema,
|
|
4
|
-
REMOTE_DEFAULTS,
|
|
5
4
|
REMOTE_LEGACY_PROFILE,
|
|
6
5
|
REMOTE_QUALITY_PROFILES,
|
|
7
6
|
RemoteInputOrder,
|
|
7
|
+
RemotePlatformRegistry,
|
|
8
8
|
SignalSchema,
|
|
9
9
|
assertDirectSignal,
|
|
10
10
|
capVideoSdp,
|
|
11
11
|
fitVideo,
|
|
12
|
-
nativeProcess,
|
|
13
|
-
parseXrandr,
|
|
14
12
|
remoteResolutionTiers,
|
|
15
|
-
remoteSessionBudget
|
|
16
|
-
|
|
17
|
-
} from "./chunk-UFLXQ4HQ.js";
|
|
13
|
+
remoteSessionBudget
|
|
14
|
+
} from "./chunk-OTKNRBZ7.js";
|
|
18
15
|
|
|
19
16
|
// src/remote-worker.ts
|
|
20
17
|
import rtc from "@roamhq/wrtc";
|
|
21
|
-
import { spawn, execFile } from "node:child_process";
|
|
22
|
-
import { promisify } from "node:util";
|
|
23
18
|
import { timingSafeEqual } from "node:crypto";
|
|
24
19
|
|
|
25
20
|
// src/remote-budget.ts
|
|
@@ -158,7 +153,6 @@ var RemoteLiveness = class {
|
|
|
158
153
|
};
|
|
159
154
|
|
|
160
155
|
// src/remote-worker.ts
|
|
161
|
-
var exec = promisify(execFile);
|
|
162
156
|
var post = (kind, data = {}) => {
|
|
163
157
|
if (process.connected) process.send?.({ kind, ...data });
|
|
164
158
|
};
|
|
@@ -207,8 +201,7 @@ var RemotePeer = class {
|
|
|
207
201
|
source = new rtc.nonstandard.RTCVideoSource({ isScreencast: true });
|
|
208
202
|
track = this.source.createTrack();
|
|
209
203
|
sender;
|
|
210
|
-
|
|
211
|
-
capture;
|
|
204
|
+
native;
|
|
212
205
|
channels = /* @__PURE__ */ new Map();
|
|
213
206
|
ready = false;
|
|
214
207
|
ended = false;
|
|
@@ -239,7 +232,6 @@ var RemotePeer = class {
|
|
|
239
232
|
legacy;
|
|
240
233
|
quality;
|
|
241
234
|
budget;
|
|
242
|
-
geometry;
|
|
243
235
|
video;
|
|
244
236
|
signal(raw) {
|
|
245
237
|
let stage = "validation";
|
|
@@ -379,155 +371,54 @@ var RemotePeer = class {
|
|
|
379
371
|
if (channel?.readyState === "open") channel.send(JSON.stringify({ ...value, remoteId: this.init.remoteId, revision: 1 }));
|
|
380
372
|
}
|
|
381
373
|
async startNative() {
|
|
382
|
-
if (process.platform === "linux") {
|
|
383
|
-
const displays = parseXrandr((await exec("xrandr", ["--listactivemonitors"], { timeout: 5e3 })).stdout);
|
|
384
|
-
if (this.ended) return;
|
|
385
|
-
const display = displays.find((d) => d.id === this.init.display.id);
|
|
386
|
-
if (!display || display.width !== this.init.display.width || display.height !== this.init.display.height) throw new Error("display");
|
|
387
|
-
this.geometry = { x: display.x, y: display.y, width: display.width, height: display.height };
|
|
388
|
-
}
|
|
389
374
|
if (this.ended) return;
|
|
390
|
-
const
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
if (lines.length > 15e5) {
|
|
401
|
-
this.stop("\u4E3B\u673A\u526A\u8D34\u677F\u8D85\u8FC7\u4E0A\u9650");
|
|
402
|
-
return;
|
|
403
|
-
}
|
|
404
|
-
let end;
|
|
405
|
-
while ((end = lines.indexOf("\n")) >= 0) {
|
|
406
|
-
const line = lines.slice(0, end);
|
|
407
|
-
lines = lines.slice(end + 1);
|
|
408
|
-
try {
|
|
409
|
-
const message = JSON.parse(line);
|
|
410
|
-
if (message.kind === "clipboard" && typeof message.id === "string" && typeof message.data === "string") {
|
|
411
|
-
const callback = this.nativePending.get(message.id);
|
|
412
|
-
this.nativePending.delete(message.id);
|
|
413
|
-
callback?.(Buffer.from(message.data, "base64").toString("utf8"));
|
|
414
|
-
}
|
|
415
|
-
if (message.kind === "clipboard-written" && this.pendingClipboard?.id === message.id) {
|
|
416
|
-
this.pendingClipboard = void 0;
|
|
417
|
-
this.send("clipboard", { kind: "clipboard.done", id: message.id });
|
|
418
|
-
}
|
|
419
|
-
if (message.kind === "clipboard-error") {
|
|
420
|
-
if (typeof message.id === "string") this.nativePending.delete(message.id);
|
|
421
|
-
this.pendingClipboard = void 0;
|
|
422
|
-
this.send("clipboard", { kind: "clipboard.error", id: message.id, message: "\u4E3B\u673A\u526A\u8D34\u677F\u8BFB\u53D6\u5931\u8D25\u6216\u8D85\u8FC7 1 MiB" });
|
|
423
|
-
}
|
|
424
|
-
if (message.kind === "input-error") this.send("control", { kind: "input.error", message: "\u4E3B\u673A\u9501\u5C4F\u72B6\u6001\u5DF2\u53D8\u5316\uFF0C\u5DF2\u505C\u6B62\u8F93\u5165\u5BC6\u7801\uFF1B\u8BF7\u68C0\u67E5\u8FDC\u7AEF\u754C\u9762" });
|
|
425
|
-
if (message.kind === "error") this.stop("\u4E3B\u673A\u6743\u9650\u3001\u663E\u793A\u5668\u6216\u56FE\u5F62\u4F1A\u8BDD\u5DF2\u53D8\u5316");
|
|
426
|
-
} catch {
|
|
427
|
-
this.stop("\u4E3B\u673A\u8F85\u52A9\u7A0B\u5E8F\u8FD4\u56DE\u65E0\u6548\u6570\u636E");
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
});
|
|
431
|
-
if (process.platform === "darwin") this.macFrames(child.stdio[3]);
|
|
432
|
-
else {
|
|
433
|
-
const g = this.geometry;
|
|
434
|
-
this.writeNative(`geometry ${g.x} ${g.y} ${g.width} ${g.height}`);
|
|
435
|
-
this.linuxFrames();
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
macFrames(stream) {
|
|
439
|
-
let buffer = Buffer.alloc(0);
|
|
440
|
-
stream.on("data", (chunk) => {
|
|
441
|
-
if (this.ended) return;
|
|
442
|
-
buffer = Buffer.concat([buffer, chunk]);
|
|
443
|
-
if (buffer.length > REMOTE_DEFAULTS.maxWidth * REMOTE_DEFAULTS.maxHeight * 3 + 24) {
|
|
444
|
-
this.stop("\u8FDC\u63A7\u753B\u9762\u7F13\u51B2\u533A\u8D85\u9650");
|
|
445
|
-
return;
|
|
446
|
-
}
|
|
447
|
-
let latest;
|
|
448
|
-
while (buffer.length >= 12) {
|
|
449
|
-
const width = buffer.readUInt32BE(0), height = buffer.readUInt32BE(4), length = buffer.readUInt32BE(8);
|
|
450
|
-
if (!width || !height || width > REMOTE_DEFAULTS.maxWidth || height > REMOTE_DEFAULTS.maxHeight || width % 2 || height % 2 || length !== width * height * 1.5) {
|
|
451
|
-
this.stop("\u4E3B\u673A\u753B\u9762\u683C\u5F0F\u65E0\u6548");
|
|
452
|
-
return;
|
|
453
|
-
}
|
|
454
|
-
if (buffer.length < length + 12) break;
|
|
455
|
-
latest = { width, height, data: new Uint8Array(buffer.subarray(12, 12 + length)) };
|
|
456
|
-
buffer = buffer.subarray(12 + length);
|
|
457
|
-
}
|
|
458
|
-
if (latest && this.direct) {
|
|
459
|
-
this.firstFrameAt ||= Date.now();
|
|
460
|
-
this.frame(latest);
|
|
461
|
-
}
|
|
462
|
-
});
|
|
463
|
-
}
|
|
464
|
-
linuxFrames() {
|
|
465
|
-
const old = this.capture;
|
|
466
|
-
this.capture = void 0;
|
|
467
|
-
old?.kill("SIGTERM");
|
|
468
|
-
const g = this.geometry, { width, height, fps } = this.video;
|
|
469
|
-
const child = spawn(this.init.config.ffmpeg, ["-nostdin", "-hide_banner", "-loglevel", "error", "-f", "x11grab", "-framerate", String(fps), "-video_size", `${g.width}x${g.height}`, "-grab_x", String(g.x), "-grab_y", String(g.y), "-draw_mouse", "1", "-i", process.env.DISPLAY, "-vf", `scale=${width}:${height}`, "-pix_fmt", "yuv420p", "-f", "rawvideo", "pipe:1"], { stdio: ["ignore", "pipe", "ignore"] });
|
|
470
|
-
this.capture = child;
|
|
471
|
-
let buffer = Buffer.alloc(0);
|
|
472
|
-
const length = width * height * 1.5;
|
|
473
|
-
child.stdout.on("data", (chunk) => {
|
|
474
|
-
if (this.capture !== child || this.ended) return;
|
|
475
|
-
buffer = Buffer.concat([buffer, chunk]);
|
|
476
|
-
if (buffer.length > length * 4) {
|
|
477
|
-
this.stop("\u4E3B\u673A\u91C7\u96C6\u7F13\u51B2\u533A\u8D85\u9650");
|
|
478
|
-
return;
|
|
479
|
-
}
|
|
480
|
-
if (buffer.length >= length) {
|
|
481
|
-
const count = Math.floor(buffer.length / length), frame = buffer.subarray((count - 1) * length, count * length);
|
|
482
|
-
if (this.direct) {
|
|
375
|
+
const adapter = RemotePlatformRegistry.standard.resolve();
|
|
376
|
+
const error = adapter.platformError();
|
|
377
|
+
if (error) throw new Error(error);
|
|
378
|
+
const session = adapter.createSession({
|
|
379
|
+
helper: this.init.helper,
|
|
380
|
+
display: this.init.display,
|
|
381
|
+
video: this.video,
|
|
382
|
+
config: this.init.config,
|
|
383
|
+
onFrame: (frame) => {
|
|
384
|
+
if (!this.ended && this.direct) {
|
|
483
385
|
this.firstFrameAt ||= Date.now();
|
|
484
|
-
this.frame(
|
|
386
|
+
this.frame(frame);
|
|
485
387
|
}
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
if (this.capture === child) this.stop("\u663E\u793A\u5668\u5DF2\u53D8\u5316\u6216 FFmpeg \u91C7\u96C6\u5931\u8D25");
|
|
388
|
+
},
|
|
389
|
+
onMessage: (message) => {
|
|
390
|
+
if (!this.ended) this.nativeMessage(message);
|
|
391
|
+
},
|
|
392
|
+
onError: (message) => this.stop(message)
|
|
492
393
|
});
|
|
394
|
+
this.native = session;
|
|
395
|
+
await session.start();
|
|
396
|
+
}
|
|
397
|
+
nativeMessage(message) {
|
|
398
|
+
if (message.kind === "clipboard" && typeof message.id === "string" && typeof message.data === "string") {
|
|
399
|
+
const callback = this.nativePending.get(message.id);
|
|
400
|
+
this.nativePending.delete(message.id);
|
|
401
|
+
callback?.(Buffer.from(message.data, "base64").toString("utf8"));
|
|
402
|
+
}
|
|
403
|
+
if (message.kind === "clipboard-written" && this.pendingClipboard?.id === message.id) {
|
|
404
|
+
this.pendingClipboard = void 0;
|
|
405
|
+
this.send("clipboard", { kind: "clipboard.done", id: message.id });
|
|
406
|
+
}
|
|
407
|
+
if (message.kind === "clipboard-error") {
|
|
408
|
+
if (typeof message.id === "string") this.nativePending.delete(message.id);
|
|
409
|
+
this.pendingClipboard = void 0;
|
|
410
|
+
this.send("clipboard", { kind: "clipboard.error", id: message.id, message: "\u4E3B\u673A\u526A\u8D34\u677F\u8BFB\u53D6\u5931\u8D25\u6216\u8D85\u8FC7 1 MiB" });
|
|
411
|
+
}
|
|
412
|
+
if (message.kind === "input-error") this.send("control", { kind: "input.error", message: "\u4E3B\u673A\u9501\u5C4F\u72B6\u6001\u5DF2\u53D8\u5316\uFF0C\u5DF2\u505C\u6B62\u8F93\u5165\u5BC6\u7801\uFF1B\u8BF7\u68C0\u67E5\u8FDC\u7AEF\u754C\u9762" });
|
|
493
413
|
}
|
|
494
414
|
frame(frame) {
|
|
495
415
|
if (this.legacy || this.frameGate.accept(frame, Date.now())) this.source.onFrame(frame);
|
|
496
416
|
}
|
|
497
417
|
writeNative(line) {
|
|
498
|
-
|
|
499
|
-
if (this.helper.stdin.writableLength > 2 * 1024 * 1024) {
|
|
500
|
-
this.stop("\u4E3B\u673A\u8F93\u5165\u5904\u7406\u8D85\u65F6");
|
|
501
|
-
return;
|
|
502
|
-
}
|
|
503
|
-
this.helper.stdin.write(`${line}
|
|
504
|
-
`);
|
|
418
|
+
this.native?.writeCommand(line);
|
|
505
419
|
}
|
|
506
420
|
input(input) {
|
|
507
|
-
|
|
508
|
-
case "pointer":
|
|
509
|
-
this.writeNative(`pointer ${input.x} ${input.y}`);
|
|
510
|
-
break;
|
|
511
|
-
case "button":
|
|
512
|
-
this.writeNative(`button ${input.x} ${input.y} ${input.button} ${input.down ? 1 : 0}`);
|
|
513
|
-
break;
|
|
514
|
-
case "wheel":
|
|
515
|
-
this.writeNative(`wheel ${input.x} ${input.y} ${Math.round(input.dx)} ${Math.round(input.dy)}`);
|
|
516
|
-
break;
|
|
517
|
-
case "key":
|
|
518
|
-
this.writeNative(`key ${input.code} ${input.down ? 1 : 0}`);
|
|
519
|
-
break;
|
|
520
|
-
case "password":
|
|
521
|
-
if (process.platform !== "darwin") return;
|
|
522
|
-
this.writeNative(`password ${Buffer.from(input.text).toString("base64")}`);
|
|
523
|
-
break;
|
|
524
|
-
case "text":
|
|
525
|
-
this.writeNative(`text ${input.terminal ? 1 : 0} ${Buffer.from(input.text).toString("base64")}`);
|
|
526
|
-
break;
|
|
527
|
-
case "release":
|
|
528
|
-
this.writeNative("release");
|
|
529
|
-
break;
|
|
530
|
-
}
|
|
421
|
+
this.native?.input(input);
|
|
531
422
|
}
|
|
532
423
|
clipboard(value) {
|
|
533
424
|
if (typeof value.id !== "string" || !/^[a-zA-Z0-9_-]{1,64}$/.test(value.id)) throw new Error("clipboard id");
|
|
@@ -609,10 +500,7 @@ var RemotePeer = class {
|
|
|
609
500
|
const video = this.budget.video(this.init.display.width, this.init.display.height);
|
|
610
501
|
const changed = video.width !== this.video.width || video.height !== this.video.height || video.fps !== this.video.fps;
|
|
611
502
|
this.video = video;
|
|
612
|
-
if (changed && this.ready)
|
|
613
|
-
if (process.platform === "darwin") this.writeNative(`video ${video.width} ${video.height} ${video.fps}`);
|
|
614
|
-
else this.linuxFrames();
|
|
615
|
-
}
|
|
503
|
+
if (changed && this.ready) this.native?.setVideo(video);
|
|
616
504
|
}
|
|
617
505
|
async encoding() {
|
|
618
506
|
if (this.parameterMode === "sdp") return;
|
|
@@ -677,9 +565,8 @@ var RemotePeer = class {
|
|
|
677
565
|
clearInterval(this.timer);
|
|
678
566
|
clearInterval(this.clipboardTimer);
|
|
679
567
|
clearTimeout(this.handshake);
|
|
680
|
-
this.
|
|
681
|
-
|
|
682
|
-
this.capture?.kill("SIGTERM");
|
|
568
|
+
this.native?.stop();
|
|
569
|
+
this.native = void 0;
|
|
683
570
|
this.track.stop();
|
|
684
571
|
for (const c of this.channels.values()) c.close();
|
|
685
572
|
this.pc.close();
|