@skylandnpm/octopus-proxy 0.1.65 → 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-6RJKEA3I.js → chunk-4CKW4ICE.js} +88 -29
- package/dist/{chunk-GS3Q6EU5.js → chunk-OTKNRBZ7.js} +312 -57
- package/dist/cli.js +3 -3
- 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";
|
|
@@ -13921,17 +13922,30 @@ var TerminalManager = class {
|
|
|
13921
13922
|
import { execFile as execFile6 } from "node:child_process";
|
|
13922
13923
|
import { promisify as promisify6 } from "node:util";
|
|
13923
13924
|
import { userInfo as userInfo4 } from "node:os";
|
|
13925
|
+
import { lstat as lstat3 } from "node:fs/promises";
|
|
13924
13926
|
var exec = promisify6(execFile6);
|
|
13925
13927
|
var PlatformDesktopSession = class {
|
|
13926
|
-
constructor(system = process.platform, environment = process.env, execute3 = async (args) => (await exec("loginctl", ["--no-ask-password", ...args], { timeout: 5e3, maxBuffer: 16384 })).stdout, account = userInfo4()) {
|
|
13928
|
+
constructor(x11Session = "logind", system = process.platform, environment = process.env, execute3 = async (args) => (await exec("loginctl", ["--no-ask-password", ...args], { timeout: 5e3, maxBuffer: 16384 })).stdout, account = userInfo4(), inspectSocket = lstat3) {
|
|
13929
|
+
this.x11Session = x11Session;
|
|
13927
13930
|
this.system = system;
|
|
13928
13931
|
this.environment = environment;
|
|
13929
13932
|
this.execute = execute3;
|
|
13930
13933
|
this.account = account;
|
|
13934
|
+
this.inspectSocket = inspectSocket;
|
|
13931
13935
|
}
|
|
13932
13936
|
async sessionIdentity() {
|
|
13933
13937
|
if (this.system === "darwin") return String(this.account.uid);
|
|
13934
13938
|
if (this.system !== "linux") throw new Error("\u6B64\u5E73\u53F0\u4E0D\u652F\u6301\u8FDC\u7A0B\u684C\u9762\u4F1A\u8BDD");
|
|
13939
|
+
if (this.x11Session === "standalone") {
|
|
13940
|
+
const display = /^(?:unix)?:([0-9]+)(?:\.([0-9]+))?$/.exec(this.environment.DISPLAY ?? "");
|
|
13941
|
+
if (!display || this.account.uid === 0 || this.environment.WAYLAND_DISPLAY || this.environment.XDG_SESSION_TYPE === "wayland") throw new Error("\u72EC\u7ACB X11 \u8FDC\u63A7\u9700\u8981\u975E root \u7528\u6237\u548C\u672C\u5730 DISPLAY\uFF08\u5982 :99\uFF09\uFF0C\u4E0D\u652F\u6301 Wayland \u6216\u8FDC\u7A0B X11");
|
|
13942
|
+
const path = `/tmp/.X11-unix/X${Number(display[1])}`;
|
|
13943
|
+
const socket = await this.inspectSocket(path).catch(() => {
|
|
13944
|
+
throw new Error(`\u72EC\u7ACB X11 socket \u4E0D\u53EF\u7528\uFF1A${path}\uFF1B\u8BF7\u5148\u542F\u52A8 Xvfb/\u684C\u9762\u670D\u52A1`);
|
|
13945
|
+
});
|
|
13946
|
+
if (!socket.isSocket()) throw new Error(`\u72EC\u7ACB X11 \u8DEF\u5F84\u4E0D\u662F socket\uFF1A${path}`);
|
|
13947
|
+
return `x11:${this.account.uid}:${Number(display[1])}.${Number(display[2] ?? 0)}:${socket.dev}:${socket.ino}:${socket.ctimeMs}`;
|
|
13948
|
+
}
|
|
13935
13949
|
const ids = this.environment.XDG_SESSION_ID ? [this.environment.XDG_SESSION_ID] : (await this.execute(["list-sessions", "--no-legend"])).trim().split("\n").map((line) => line.trim().split(/\s+/)[0]).filter(Boolean);
|
|
13936
13950
|
const sessions = [];
|
|
13937
13951
|
for (const id of ids) {
|
|
@@ -13946,8 +13960,49 @@ var PlatformDesktopSession = class {
|
|
|
13946
13960
|
|
|
13947
13961
|
// src/remote-manager.ts
|
|
13948
13962
|
import { fork } from "node:child_process";
|
|
13949
|
-
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";
|
|
13950
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
|
+
}
|
|
13951
14006
|
|
|
13952
14007
|
// src/remote-stun.ts
|
|
13953
14008
|
import { Resolver } from "node:dns/promises";
|
|
@@ -14014,13 +14069,13 @@ async function discoverRemoteStunUrls(hubUrl, configured, resolve5 = resolvePubl
|
|
|
14014
14069
|
|
|
14015
14070
|
// src/remote-manager.ts
|
|
14016
14071
|
function spawnWorker(message) {
|
|
14017
|
-
const worker =
|
|
14072
|
+
const worker = fileURLToPath5(new URL(import.meta.url.endsWith(".ts") ? "./remote-worker.ts" : "./remote-worker.js", import.meta.url));
|
|
14018
14073
|
const child = fork(worker, [], { stdio: ["ignore", "ignore", "ignore", "ipc"], execArgv: import.meta.url.endsWith(".ts") ? ["--import", "tsx"] : [] });
|
|
14019
14074
|
child.send({ kind: "init", ...message });
|
|
14020
14075
|
return child;
|
|
14021
14076
|
}
|
|
14022
14077
|
var RemoteManager = class {
|
|
14023
|
-
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) {
|
|
14024
14079
|
this.host = host;
|
|
14025
14080
|
this.desktop = desktop;
|
|
14026
14081
|
this.desktopSession = desktopSession;
|
|
@@ -14029,22 +14084,25 @@ var RemoteManager = class {
|
|
|
14029
14084
|
this.workerFactory = workerFactory;
|
|
14030
14085
|
this.probe = probe;
|
|
14031
14086
|
this.discoverStun = discoverStun;
|
|
14087
|
+
this.platform = platformRegistry.resolve();
|
|
14032
14088
|
this.config = RemoteConfigSchema.parse(host.desktopRemote ?? {});
|
|
14033
14089
|
}
|
|
14034
14090
|
current;
|
|
14035
14091
|
stopping = false;
|
|
14036
14092
|
config;
|
|
14093
|
+
platform;
|
|
14037
14094
|
stunUrls() {
|
|
14038
14095
|
return this.config.stunUrls ?? defaultRemoteStunUrls(this.host.hubUrl);
|
|
14039
14096
|
}
|
|
14040
14097
|
async status() {
|
|
14041
|
-
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 } };
|
|
14042
14099
|
if (!this.config.enabled) return { ...base, available: false, message: "\u4E3B\u673A\u5DF2\u5173\u95ED\u8FDC\u7A0B\u63A7\u5236" };
|
|
14043
14100
|
try {
|
|
14044
|
-
const permissions = await this.probe(this.host.stateDirectory, this.config);
|
|
14045
|
-
const
|
|
14046
|
-
const available =
|
|
14047
|
-
|
|
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;
|
|
14104
|
+
if (available) await this.desktopSession.sessionIdentity();
|
|
14105
|
+
return { ...base, available, permissions, ...!available ? { message } : {} };
|
|
14048
14106
|
} catch (error) {
|
|
14049
14107
|
return { ...base, available: false, message: error instanceof Error ? error.message : "\u8FDC\u63A7\u4F9D\u8D56\u4E0D\u53EF\u7528" };
|
|
14050
14108
|
}
|
|
@@ -14080,15 +14138,16 @@ var RemoteManager = class {
|
|
|
14080
14138
|
if (this.current) throw new Error("\u4E3B\u673A\u6B63\u5728\u88AB\u63A7\u5236");
|
|
14081
14139
|
const quality = RemoteQualitySchema.parse(payload.quality ?? "hd");
|
|
14082
14140
|
const budget = remoteSessionBudget(this.config, quality, payload.quality === void 0);
|
|
14083
|
-
const session = { remoteId:
|
|
14141
|
+
const session = { remoteId: randomUUID19(), owner, closed: false, signaling: Promise.resolve(), signals: 0, signalWindow: Date.now(), candidates: 0 };
|
|
14084
14142
|
this.current = session;
|
|
14085
14143
|
try {
|
|
14086
14144
|
const [permissions, stunUrls] = await Promise.all([
|
|
14087
|
-
this.probe(this.host.stateDirectory, this.config),
|
|
14145
|
+
this.probe(this.host.stateDirectory, this.config, this.platform),
|
|
14088
14146
|
this.discoverStun(this.host.hubUrl, this.config.stunUrls)
|
|
14089
14147
|
]);
|
|
14090
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");
|
|
14091
|
-
|
|
14149
|
+
const unavailable = this.platform.unavailableReason(permissions);
|
|
14150
|
+
if (unavailable) throw new Error(unavailable);
|
|
14092
14151
|
const identity = await this.desktopSession.sessionIdentity();
|
|
14093
14152
|
const display = (await this.desktop.list()).find((d) => d.id === payload.displayId);
|
|
14094
14153
|
if (!display) throw new Error("\u663E\u793A\u5668\u5DF2\u65AD\u5F00\uFF0C\u8BF7\u5237\u65B0\u663E\u793A\u5668\u5217\u8868");
|
|
@@ -14159,16 +14218,16 @@ var RemoteManager = class {
|
|
|
14159
14218
|
};
|
|
14160
14219
|
|
|
14161
14220
|
// src/tool-selection.ts
|
|
14162
|
-
import { randomBytes as randomBytes3, randomUUID as
|
|
14163
|
-
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";
|
|
14164
14223
|
import { platform as platform5 } from "node:os";
|
|
14165
14224
|
import { isDeepStrictEqual as isDeepStrictEqual2 } from "node:util";
|
|
14166
14225
|
|
|
14167
14226
|
// src/node-resolution.ts
|
|
14168
|
-
import { realpath as realpath7, stat as
|
|
14227
|
+
import { realpath as realpath7, stat as stat11 } from "node:fs/promises";
|
|
14169
14228
|
import { execFile as execFile7 } from "node:child_process";
|
|
14170
14229
|
import { promisify as promisify7 } from "node:util";
|
|
14171
|
-
import { join as
|
|
14230
|
+
import { join as join15 } from "node:path";
|
|
14172
14231
|
import { homedir as homedir2, platform as platform4 } from "node:os";
|
|
14173
14232
|
var run4 = promisify7(execFile7);
|
|
14174
14233
|
var VERSION_MANAGED_SEGMENTS = /* @__PURE__ */ new Set([".nvm", "nvm", ".volta", "volta", ".fnm", "fnm", ".asdf", "asdf", ".mise", "mise"]);
|
|
@@ -14212,15 +14271,15 @@ async function resolveNodePath(options = {}) {
|
|
|
14212
14271
|
return { nodePath: null, source: "exec" };
|
|
14213
14272
|
}
|
|
14214
14273
|
function toolSearchDirs(kind) {
|
|
14215
|
-
const dirs = [...describeTool(kind).searchDirs,
|
|
14274
|
+
const dirs = [...describeTool(kind).searchDirs, join15(homedir2(), ".local", "bin"), join15(homedir2(), ".opencode", "bin"), join15(homedir2(), ".codex", "bin")];
|
|
14216
14275
|
return [...new Set(dirs)];
|
|
14217
14276
|
}
|
|
14218
14277
|
async function resolveCommandOnPath(name, extraDirs = []) {
|
|
14219
14278
|
const dirs = [...(process.env.PATH ?? "").split(":"), ...extraDirs].filter(Boolean);
|
|
14220
14279
|
for (const dir of new Set(dirs)) {
|
|
14221
|
-
const full =
|
|
14280
|
+
const full = join15(dir, name);
|
|
14222
14281
|
try {
|
|
14223
|
-
const info = await
|
|
14282
|
+
const info = await stat11(full);
|
|
14224
14283
|
if (info.isFile() && info.mode & 73) return full;
|
|
14225
14284
|
} catch {
|
|
14226
14285
|
}
|
|
@@ -14303,7 +14362,7 @@ var ToolSelection = class {
|
|
|
14303
14362
|
});
|
|
14304
14363
|
}
|
|
14305
14364
|
modelCommand(projectId) {
|
|
14306
|
-
return { type: "command", id:
|
|
14365
|
+
return { type: "command", id: randomUUID20(), botId: this.config.botId, operation: "configuration.models", projectId, payload: {} };
|
|
14307
14366
|
}
|
|
14308
14367
|
profiles(raw) {
|
|
14309
14368
|
return raw.toolProfiles && typeof raw.toolProfiles === "object" && !Array.isArray(raw.toolProfiles) ? raw.toolProfiles : {};
|
|
@@ -14345,7 +14404,7 @@ var ToolSelection = class {
|
|
|
14345
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) {
|
|
14346
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");
|
|
14347
14406
|
}
|
|
14348
|
-
return JSON.parse(await
|
|
14407
|
+
return JSON.parse(await readFile11(this.configPath, "utf8"));
|
|
14349
14408
|
}
|
|
14350
14409
|
async release(instance) {
|
|
14351
14410
|
instance.adapter.close();
|
|
@@ -14362,14 +14421,14 @@ var ToolSelection = class {
|
|
|
14362
14421
|
// src/daemon.ts
|
|
14363
14422
|
async function runDaemon(configPath, authenticate = verifyHostPassword) {
|
|
14364
14423
|
const config = await loadConfig(configPath);
|
|
14365
|
-
await
|
|
14366
|
-
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"));
|
|
14367
14426
|
let secureChat;
|
|
14368
14427
|
function emit(projectId, event, data) {
|
|
14369
14428
|
secureChat?.publish(projectId, event, data);
|
|
14370
14429
|
}
|
|
14371
14430
|
const terminals = new TerminalManager(workspace, (owner, project, event, data) => secureChat?.sendTo(owner, project, event, data));
|
|
14372
|
-
const desktopBackend = new PlatformDesktop(config.stateDirectory), desktopSession = new PlatformDesktopSession();
|
|
14431
|
+
const desktopBackend = new PlatformDesktop(config.stateDirectory), desktopSession = new PlatformDesktopSession(config.desktopRemote?.x11Session);
|
|
14373
14432
|
const desktop = new DesktopManager(desktopBackend);
|
|
14374
14433
|
const remote = new RemoteManager(
|
|
14375
14434
|
config,
|
|
@@ -14385,7 +14444,7 @@ async function runDaemon(configPath, authenticate = verifyHostPassword) {
|
|
|
14385
14444
|
});
|
|
14386
14445
|
return { runtime, adapter };
|
|
14387
14446
|
});
|
|
14388
|
-
const artifacts = new Artifacts(workspace,
|
|
14447
|
+
const artifacts = new Artifacts(workspace, join16(config.stateDirectory, "artifacts"), emit);
|
|
14389
14448
|
const projectRegistry = () => new ProjectRegistry(configPath, config, tools.current.runtime, tools.current.adapter, (resetProjects) => {
|
|
14390
14449
|
for (const id of resetProjects) terminals.closeProject(id);
|
|
14391
14450
|
});
|
|
@@ -4104,7 +4104,8 @@ var RemoteConfigSchema = z.object({
|
|
|
4104
4104
|
stunUrls: z.array(stunUrl).min(1).max(4).optional(),
|
|
4105
4105
|
totalBitrate: z.number().int().min(1e5).max(REMOTE_DEFAULTS.totalBitrate).default(REMOTE_DEFAULTS.totalBitrate),
|
|
4106
4106
|
videoMaxBitrate: z.number().int().min(5e4).max(REMOTE_DEFAULTS.videoMaxBitrate).default(REMOTE_DEFAULTS.videoMaxBitrate),
|
|
4107
|
-
ffmpeg: z.string().min(1).default("ffmpeg")
|
|
4107
|
+
ffmpeg: z.string().min(1).default("ffmpeg"),
|
|
4108
|
+
x11Session: z.enum(["logind", "standalone"]).default("logind")
|
|
4108
4109
|
}).strict().refine((c) => c.videoMaxBitrate <= c.totalBitrate * 0.8, "\u89C6\u9891\u989D\u5EA6\u4E0D\u5F97\u8D85\u8FC7\u603B\u9884\u7B97\u7684 80%");
|
|
4109
4110
|
function remoteSessionBudget(config, quality, legacy = false) {
|
|
4110
4111
|
const profile = legacy ? REMOTE_LEGACY_PROFILE : REMOTE_QUALITY_PROFILES[quality];
|
|
@@ -4448,61 +4449,16 @@ var DesktopManager = class {
|
|
|
4448
4449
|
}
|
|
4449
4450
|
};
|
|
4450
4451
|
|
|
4451
|
-
// src/remote-
|
|
4452
|
+
// src/remote/native-process.ts
|
|
4452
4453
|
import { execFile as execFile2, spawn } from "node:child_process";
|
|
4453
4454
|
import { promisify as promisify2 } from "node:util";
|
|
4454
|
-
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";
|
|
4455
|
-
import { join as join2 } from "node:path";
|
|
4456
|
-
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
4457
|
-
import { createHash as createHash2, randomUUID as randomUUID2 } from "node:crypto";
|
|
4458
4455
|
var exec = promisify2(execFile2);
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
const source = fileURLToPath2(new URL(mac ? "../native/remote-macos.m" : "../native/remote-x11.c", import.meta.url));
|
|
4466
|
-
const target = join2(stateDirectory, "octopus-remote-desktop");
|
|
4467
|
-
const stamp = `${target}.sha256`;
|
|
4468
|
-
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"];
|
|
4469
|
-
const fingerprint = createHash2("sha256").update(await readFile2(source)).update(JSON.stringify([process.platform, process.arch, flags])).digest("hex");
|
|
4470
|
-
const existing = await stat2(target).catch(() => void 0);
|
|
4471
|
-
if (existing?.isFile() && (existing.mode & 63) === 0 && await readFile2(stamp, "utf8").catch(() => "") === fingerprint) return target;
|
|
4472
|
-
const temporary = `${target}.${randomUUID2()}`, temporaryStamp = `${temporary}.sha256`;
|
|
4473
|
-
const args = mac ? [...flags, source, "-o", temporary] : ["-O2", source, "-o", temporary, "-lX11", "-lXtst"];
|
|
4474
|
-
try {
|
|
4475
|
-
await exec(mac ? "/usr/bin/clang" : "cc", args, { timeout: 6e4, maxBuffer: 16384 });
|
|
4476
|
-
await chmod2(temporary, 448);
|
|
4477
|
-
await writeFile2(temporaryStamp, fingerprint, { mode: 384 });
|
|
4478
|
-
await rename2(temporary, target);
|
|
4479
|
-
await rename2(temporaryStamp, stamp);
|
|
4480
|
-
return target;
|
|
4481
|
-
} catch {
|
|
4482
|
-
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");
|
|
4483
|
-
} finally {
|
|
4484
|
-
await Promise.all([rm2(temporary, { force: true }), rm2(temporaryStamp, { force: true })]);
|
|
4485
|
-
}
|
|
4486
|
-
}
|
|
4487
|
-
async function probeRemote(stateDirectory, config) {
|
|
4488
|
-
if (!supportedRemotePlatform()) throw new Error("\u8FDC\u63A7\u9996\u7248\u652F\u6301 macOS 12.3+ \u4E0E Linux X11 x64\uFF1BWayland\u3001\u65E0\u56FE\u5F62\u4F1A\u8BDD\u6682\u4E0D\u652F\u6301");
|
|
4489
|
-
await import("@roamhq/wrtc").catch(() => {
|
|
4490
|
-
throw new Error("WebRTC \u539F\u751F\u4F9D\u8D56\u4E0D\u53EF\u7528\uFF0C\u8BF7\u91CD\u65B0\u5B89\u88C5\u65B0\u7248 Proxy");
|
|
4491
|
-
});
|
|
4492
|
-
const helper = await ensureRemoteHelper(stateDirectory);
|
|
4493
|
-
const result = JSON.parse((await exec(helper, ["check"], { timeout: 5e3, maxBuffer: 4096 })).stdout);
|
|
4494
|
-
if (process.platform === "linux") {
|
|
4495
|
-
const version = await exec(config.ffmpeg, ["-version"], { timeout: 5e3, maxBuffer: 32768 }).catch(() => {
|
|
4496
|
-
throw new Error("\u8BF7\u5B89\u88C5\u652F\u6301 x11grab \u7684 LGPL FFmpeg");
|
|
4497
|
-
});
|
|
4498
|
-
assertLgplFFmpeg(version.stdout);
|
|
4499
|
-
const devices = await exec(config.ffmpeg, ["-hide_banner", "-devices"], { timeout: 5e3, maxBuffer: 32768 });
|
|
4500
|
-
if (!devices.stdout.includes("x11grab")) throw new Error("FFmpeg \u7F3A\u5C11 x11grab \u91C7\u96C6\u652F\u6301");
|
|
4501
|
-
}
|
|
4502
|
-
return { helper, ...result };
|
|
4503
|
-
}
|
|
4504
|
-
function nativeProcess(helper, displayId, video) {
|
|
4505
|
-
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"] });
|
|
4506
4462
|
}
|
|
4507
4463
|
function terminateNative(child) {
|
|
4508
4464
|
if (!child) return;
|
|
@@ -4513,6 +4469,307 @@ function terminateNative(child) {
|
|
|
4513
4469
|
child.once("exit", () => clearTimeout(timer));
|
|
4514
4470
|
}
|
|
4515
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
|
+
|
|
4516
4773
|
export {
|
|
4517
4774
|
__commonJS,
|
|
4518
4775
|
__toESM,
|
|
@@ -4533,10 +4790,8 @@ export {
|
|
|
4533
4790
|
DESKTOP_PERMISSION_GUIDANCE,
|
|
4534
4791
|
requestDesktopPermission,
|
|
4535
4792
|
checkDesktop,
|
|
4536
|
-
parseXrandr,
|
|
4537
4793
|
PlatformDesktop,
|
|
4538
4794
|
DesktopManager,
|
|
4539
|
-
|
|
4540
|
-
|
|
4541
|
-
terminateNative
|
|
4795
|
+
nativeDependencies,
|
|
4796
|
+
RemotePlatformRegistry
|
|
4542
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";
|
|
@@ -364,7 +364,7 @@ async function runDoctor(opts) {
|
|
|
364
364
|
}
|
|
365
365
|
if (config) {
|
|
366
366
|
results.push(...await checkDesktop(config.stateDirectory));
|
|
367
|
-
const remote = new RemoteManager(config, new PlatformDesktop(config.stateDirectory), new PlatformDesktopSession(), () => {
|
|
367
|
+
const remote = new RemoteManager(config, new PlatformDesktop(config.stateDirectory), new PlatformDesktopSession(config.desktopRemote?.x11Session), () => {
|
|
368
368
|
}, () => false);
|
|
369
369
|
try {
|
|
370
370
|
const status = await remote.status();
|
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-GS3Q6EU5.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();
|