@skylandnpm/octopus-proxy 0.1.57 → 0.1.60
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-XIJKEY4L.js → chunk-GS3Q6EU5.js} +2 -2
- package/dist/{chunk-YFY544PH.js → chunk-VW545PK2.js} +69 -8
- package/dist/cli.js +2 -2
- package/dist/main.js +2 -2
- package/dist/remote-worker.js +1 -1
- package/native/desktop-macos.m +43 -0
- package/native/remote-macos.m +77 -4
- package/package.json +1 -1
|
@@ -4257,7 +4257,7 @@ var PlatformDesktop = class {
|
|
|
4257
4257
|
await mkdir(this.stateDirectory, { recursive: true, mode: 448 });
|
|
4258
4258
|
const target = join(this.stateDirectory, "octopus-desktop-capture");
|
|
4259
4259
|
const source = fileURLToPath(new URL("../native/desktop-macos.m", import.meta.url));
|
|
4260
|
-
const flags = ["-O2", "-fobjc-arc", "-mmacosx-version-min=12.3", "-framework", "Foundation", "-framework", "AppKit", "-framework", "ScreenCaptureKit", "-framework", "CoreMedia", "-framework", "CoreVideo", "-framework", "VideoToolbox", "-framework", "ImageIO"];
|
|
4260
|
+
const flags = ["-O2", "-fobjc-arc", "-mmacosx-version-min=12.3", "-framework", "Foundation", "-framework", "AppKit", "-framework", "ScreenCaptureKit", "-framework", "CoreMedia", "-framework", "CoreVideo", "-framework", "VideoToolbox", "-framework", "ImageIO", "-framework", "IOKit"];
|
|
4261
4261
|
const fingerprint = createHash("sha256").update(await readFile(source)).update(JSON.stringify([process.arch, flags])).digest("hex");
|
|
4262
4262
|
const stamp = `${target}.sha256`, existing = await stat(target).catch(() => null);
|
|
4263
4263
|
if (existing?.isFile() && (existing.mode & 63) === 0 && await readFile(stamp, "utf8").catch(() => "") === fingerprint) return target;
|
|
@@ -4465,7 +4465,7 @@ async function ensureRemoteHelper(stateDirectory) {
|
|
|
4465
4465
|
const source = fileURLToPath2(new URL(mac ? "../native/remote-macos.m" : "../native/remote-x11.c", import.meta.url));
|
|
4466
4466
|
const target = join2(stateDirectory, "octopus-remote-desktop");
|
|
4467
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"] : ["-O2", "-lX11", "-lXtst"];
|
|
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
4469
|
const fingerprint = createHash2("sha256").update(await readFile2(source)).update(JSON.stringify([process.platform, process.arch, flags])).digest("hex");
|
|
4470
4470
|
const existing = await stat2(target).catch(() => void 0);
|
|
4471
4471
|
if (existing?.isFile() && (existing.mode & 63) === 0 && await readFile2(stamp, "utf8").catch(() => "") === fingerprint) return target;
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
probeRemote,
|
|
13
13
|
remoteSessionBudget,
|
|
14
14
|
z
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-GS3Q6EU5.js";
|
|
16
16
|
|
|
17
17
|
// ../../node_modules/.pnpm/ajv@8.17.1/node_modules/ajv/dist/compile/codegen/code.js
|
|
18
18
|
var require_code = __commonJS({
|
|
@@ -11270,6 +11270,37 @@ function zcodeEnvironment(command) {
|
|
|
11270
11270
|
return env;
|
|
11271
11271
|
}
|
|
11272
11272
|
|
|
11273
|
+
// src/tools/zcode-standalone.ts
|
|
11274
|
+
import { basename as basename4 } from "node:path";
|
|
11275
|
+
function standaloneZcodeSource(source) {
|
|
11276
|
+
const bootstrap = /\br\(([$\w]+),"runZCodeProtocolAgent"\)/.exec(source)?.[1];
|
|
11277
|
+
const registry = /\br\(([$\w]+),"startProcessProviderRegistryRuntime"\)/.exec(source)?.[1];
|
|
11278
|
+
if (!bootstrap || !registry) throw new Error("Unsupported ZCode bundle: native account bootstrap is unavailable");
|
|
11279
|
+
const start = source.indexOf(`async function ${bootstrap}(`);
|
|
11280
|
+
const end = source.indexOf(`r(${bootstrap},"runZCodeProtocolAgent")`, start);
|
|
11281
|
+
if (start < 0 || end < start) throw new Error("Unsupported ZCode protocol bootstrap");
|
|
11282
|
+
let body = source.slice(start, end);
|
|
11283
|
+
const escapedRegistry = registry.replace(/[$]/g, "\\$&");
|
|
11284
|
+
const registryCall = new RegExp(`\\b${escapedRegistry}\\(([$\\w]+)\\)`, "g");
|
|
11285
|
+
const nativePorts = /([$\w]+)\.runtime\.registryService,\1\.configuredDefaultModelSelection\),/g;
|
|
11286
|
+
if ([...body.matchAll(registryCall)].length !== 1 || [...body.matchAll(nativePorts)].length !== 1) {
|
|
11287
|
+
throw new Error("Unsupported ZCode account bootstrap; native compatibility check failed");
|
|
11288
|
+
}
|
|
11289
|
+
body = body.replace(registryCall, `${registry}($1,{standalone:{}})`).replace(nativePorts, "$&providerRuntimeHeadersPort:$1.providerRuntimeHeadersPort,");
|
|
11290
|
+
return source.slice(0, start) + body + source.slice(end);
|
|
11291
|
+
}
|
|
11292
|
+
function zcodeServerLaunch(command, args) {
|
|
11293
|
+
if (basename4(command) !== "zcode.cjs") return { command, args: [...args] };
|
|
11294
|
+
const shim = `const fs=require('node:fs'),Module=require('node:module'),path=require('node:path');
|
|
11295
|
+
const filename=process.argv[1];
|
|
11296
|
+
try {
|
|
11297
|
+
const source=(${standaloneZcodeSource.toString()})(fs.readFileSync(filename,'utf8'));
|
|
11298
|
+
const entry=new Module(filename);entry.filename=filename;entry.paths=Module._nodeModulePaths(path.dirname(filename));
|
|
11299
|
+
entry._compile(source,filename);
|
|
11300
|
+
} catch(error) { console.error(error.message);process.exitCode=1; }`;
|
|
11301
|
+
return { command: process.execPath, args: ["-e", shim, command, ...args] };
|
|
11302
|
+
}
|
|
11303
|
+
|
|
11273
11304
|
// src/tools/stdio-runtime.ts
|
|
11274
11305
|
import { spawn as spawn3, execFile as execFile4 } from "node:child_process";
|
|
11275
11306
|
import { promisify as promisify4 } from "node:util";
|
|
@@ -11642,8 +11673,9 @@ ${bytes.toString("utf8")}`);
|
|
|
11642
11673
|
await mkdir7(this.config.stateDirectory, { recursive: true, mode: 448 });
|
|
11643
11674
|
const log = await open6(logPath, "a", 384);
|
|
11644
11675
|
let child;
|
|
11676
|
+
const launch = this.kind === "zcode" ? zcodeServerLaunch(this.config.tool.command, this.descriptor.serverArgs) : { command: this.config.tool.command, args: [...this.descriptor.serverArgs] };
|
|
11645
11677
|
try {
|
|
11646
|
-
child = spawn3(
|
|
11678
|
+
child = spawn3(launch.command, launch.args, { cwd: directory, env: this.kind === "zcode" ? zcodeEnvironment(this.config.tool.command) : { ...process.env }, stdio: ["pipe", "pipe", log.fd] });
|
|
11647
11679
|
} catch (error) {
|
|
11648
11680
|
await log.close();
|
|
11649
11681
|
throw error;
|
|
@@ -12297,6 +12329,7 @@ var ZcodeAdapter = class {
|
|
|
12297
12329
|
permissions = /* @__PURE__ */ new Map();
|
|
12298
12330
|
inputs = /* @__PURE__ */ new Map();
|
|
12299
12331
|
subscribed = /* @__PURE__ */ new Set();
|
|
12332
|
+
resuming = /* @__PURE__ */ new Map();
|
|
12300
12333
|
supportedOperations() {
|
|
12301
12334
|
return OPERATIONS2;
|
|
12302
12335
|
}
|
|
@@ -12381,6 +12414,13 @@ var ZcodeAdapter = class {
|
|
|
12381
12414
|
const turns = /* @__PURE__ */ new Map();
|
|
12382
12415
|
let current;
|
|
12383
12416
|
for (const row of rows) {
|
|
12417
|
+
if (row.info && ["user", "assistant"].includes(String(row.info.role)) && Array.isArray(row.parts)) {
|
|
12418
|
+
const semantics = row.info.semantics;
|
|
12419
|
+
if (semantics?.uiVisibility === "hidden" || semantics?.uiVisibility !== "visible" && row.info.visibility === "model-only") continue;
|
|
12420
|
+
messages.push({ info: { ...row.info }, parts: row.parts });
|
|
12421
|
+
current = void 0;
|
|
12422
|
+
continue;
|
|
12423
|
+
}
|
|
12384
12424
|
const data = row.data ?? row;
|
|
12385
12425
|
if (data.kind === "turnHeader") {
|
|
12386
12426
|
turns.set(String(row.turnId ?? data.turnId ?? ""), data);
|
|
@@ -12409,7 +12449,27 @@ var ZcodeAdapter = class {
|
|
|
12409
12449
|
async session(project, id) {
|
|
12410
12450
|
if (!describeTool("zcode").sessionPattern.test(id)) throw new Error("Invalid session id");
|
|
12411
12451
|
const rpc = await this.rpc(project);
|
|
12412
|
-
|
|
12452
|
+
let result;
|
|
12453
|
+
try {
|
|
12454
|
+
result = await rpc.request("session/read", { sessionId: id });
|
|
12455
|
+
} catch (error) {
|
|
12456
|
+
if (!(error instanceof Error) || !/^Session is not active:/i.test(error.message)) throw error;
|
|
12457
|
+
const key = `${project}\0${id}`;
|
|
12458
|
+
let pending = this.resuming.get(key);
|
|
12459
|
+
if (!pending) {
|
|
12460
|
+
pending = (async () => {
|
|
12461
|
+
const list = await rpc.request("session/list", { workspace: await this.workspaceRef(project), sessionIds: [id] });
|
|
12462
|
+
if (!list.sessions?.some((session) => session.sessionId === id)) throw new Error("Session not found in this project");
|
|
12463
|
+
return rpc.request("session/resume", { sessionId: id });
|
|
12464
|
+
})();
|
|
12465
|
+
this.resuming.set(key, pending);
|
|
12466
|
+
}
|
|
12467
|
+
try {
|
|
12468
|
+
result = await pending;
|
|
12469
|
+
} finally {
|
|
12470
|
+
if (this.resuming.get(key) === pending) this.resuming.delete(key);
|
|
12471
|
+
}
|
|
12472
|
+
}
|
|
12413
12473
|
const sessionInfo = result.session;
|
|
12414
12474
|
if (!sessionInfo) throw new Error("Session not found");
|
|
12415
12475
|
const directory = sessionInfo.workspace?.workspacePath;
|
|
@@ -12438,7 +12498,7 @@ var ZcodeAdapter = class {
|
|
|
12438
12498
|
const snapshot = await rpc.request("session/create", { workspace: await this.workspaceRef(project), persistence: "deferred", titleGenerationEnabled: false, toolAllowlist: [] });
|
|
12439
12499
|
try {
|
|
12440
12500
|
const model = snapshot.settings.model;
|
|
12441
|
-
if (op === "auth.status") return { status: model.available.length ? "authenticated" : "unauthenticated", message: model.available.length ? "ZCode \u5DF2\u914D\u7F6E\u53EF\u7528\u6A21\u578B\uFF08\u51ED\u636E\u6709\u6548\u6027\u7531\u5DE5\u5177\u5728\u8C03\u7528\u65F6\u68C0\u67E5\uFF09" : "ZCode \u672A\u914D\u7F6E\u53EF\u7528\u6A21\u578B", methods: [{ id: "zai", label: "Z.AI \u6D4F\u89C8\u5668\u767B\u5F55" }] };
|
|
12501
|
+
if (op === "auth.status") return { status: model.available.length ? "authenticated" : "unauthenticated", message: model.available.length ? "ZCode \u5DF2\u914D\u7F6E\u53EF\u7528\u6A21\u578B\uFF08\u51ED\u636E\u6709\u6548\u6027\u7531\u5DE5\u5177\u5728\u8C03\u7528\u65F6\u68C0\u67E5\uFF09" : "ZCode \u672A\u914D\u7F6E\u53EF\u7528\u6A21\u578B", methods: [{ id: "zai", label: "Z.AI \u6D4F\u89C8\u5668\u767B\u5F55" }, { id: "bigmodel", label: "\u667A\u8C31 BigModel \u6D4F\u89C8\u5668\u767B\u5F55" }] };
|
|
12442
12502
|
const providers = /* @__PURE__ */ new Map();
|
|
12443
12503
|
for (const entry of model.available) {
|
|
12444
12504
|
const provider = providers.get(entry.ref.providerId) ?? { id: entry.ref.providerId, name: entry.providerLabel, models: /* @__PURE__ */ Object.create(null) };
|
|
@@ -12451,20 +12511,21 @@ var ZcodeAdapter = class {
|
|
|
12451
12511
|
}
|
|
12452
12512
|
}
|
|
12453
12513
|
if (op === "auth.login") {
|
|
12454
|
-
if (p.method !== "zai") throw new Error("\u4E0D\u652F\u6301\u7684 ZCode \u767B\u5F55\u65B9\u5F0F");
|
|
12514
|
+
if (p.method !== "zai" && p.method !== "bigmodel") throw new Error("\u4E0D\u652F\u6301\u7684 ZCode \u767B\u5F55\u65B9\u5F0F");
|
|
12515
|
+
const provider = p.method;
|
|
12455
12516
|
let child;
|
|
12456
12517
|
const flow = this.auth.create(project, () => {
|
|
12457
12518
|
child?.kill("SIGTERM");
|
|
12458
12519
|
});
|
|
12459
|
-
child = spawn4(this.runtime.config.tool.command, ["login", "--no-browser", "--json"], { cwd: await this.workspace.root(project), env: zcodeEnvironment(this.runtime.config.tool.command), stdio: ["ignore", "pipe", "pipe"] });
|
|
12520
|
+
child = spawn4(this.runtime.config.tool.command, ["login", provider, "--no-browser", "--json"], { cwd: await this.workspace.root(project), env: zcodeEnvironment(this.runtime.config.tool.command), stdio: ["ignore", "pipe", "pipe"] });
|
|
12460
12521
|
let output = "", diagnostic = "";
|
|
12461
12522
|
child.stdout?.on("data", (data) => {
|
|
12462
12523
|
output = (output + data.toString()).slice(-65536);
|
|
12463
12524
|
});
|
|
12464
12525
|
child.stderr?.on("data", (data) => {
|
|
12465
12526
|
diagnostic = (diagnostic + data.toString()).slice(-8192);
|
|
12466
|
-
const match = /Open this URL to sign in:\s*(https?:\/\/[^\s]+)/.exec(diagnostic);
|
|
12467
|
-
if (match) this.auth.update(project, flow.flowId, { url: match[1], message:
|
|
12527
|
+
const match = /(?:Open this URL to sign in|Fallback URL):\s*(https?:\/\/[^\s]+)/.exec(diagnostic);
|
|
12528
|
+
if (match) this.auth.update(project, flow.flowId, { url: match[1], message: `\u5728\u6D4F\u89C8\u5668\u5B8C\u6210 ${provider === "bigmodel" ? "\u667A\u8C31 BigModel" : "Z.AI"} \u6388\u6743\uFF0CZCode \u5C06\u81EA\u884C\u4FDD\u5B58\u8BA4\u8BC1\u4FE1\u606F` });
|
|
12468
12529
|
});
|
|
12469
12530
|
child.once("error", () => this.auth.update(project, flow.flowId, { status: "failed", message: "\u65E0\u6CD5\u542F\u52A8 ZCode \u539F\u751F\u767B\u5F55" }));
|
|
12470
12531
|
child.once("exit", (code) => {
|
package/dist/cli.js
CHANGED
|
@@ -34,13 +34,13 @@ import {
|
|
|
34
34
|
supportedPlatform,
|
|
35
35
|
systemdUnitPath,
|
|
36
36
|
toolSearchDirs
|
|
37
|
-
} from "./chunk-
|
|
37
|
+
} from "./chunk-VW545PK2.js";
|
|
38
38
|
import {
|
|
39
39
|
DESKTOP_PERMISSION_GUIDANCE,
|
|
40
40
|
PlatformDesktop,
|
|
41
41
|
checkDesktop,
|
|
42
42
|
requestDesktopPermission
|
|
43
|
-
} from "./chunk-
|
|
43
|
+
} from "./chunk-GS3Q6EU5.js";
|
|
44
44
|
|
|
45
45
|
// src/cli.ts
|
|
46
46
|
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-VW545PK2.js";
|
|
6
|
+
import "./chunk-GS3Q6EU5.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
package/native/desktop-macos.m
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
#import <ScreenCaptureKit/ScreenCaptureKit.h>
|
|
3
3
|
#import <ImageIO/ImageIO.h>
|
|
4
4
|
#import <VideoToolbox/VideoToolbox.h>
|
|
5
|
+
#import <IOKit/pwr_mgt/IOPMLib.h>
|
|
6
|
+
#include <dlfcn.h>
|
|
7
|
+
#include <unistd.h>
|
|
5
8
|
|
|
6
9
|
static BOOL finished = NO;
|
|
7
10
|
static int resultCode = 1;
|
|
@@ -39,6 +42,41 @@ static BOOL saveImage(CGImageRef image, NSString *path) {
|
|
|
39
42
|
return saved;
|
|
40
43
|
}
|
|
41
44
|
|
|
45
|
+
// The public Quartz snapshot API is used only on 12.3–13, where SCStream can
|
|
46
|
+
// stall at the lock screen. Resolve it lazily: newer macOS removed this API.
|
|
47
|
+
static BOOL legacyLockedSession(void) {
|
|
48
|
+
NSDictionary *s = CFBridgingRelease(CGSessionCopyCurrentDictionary());
|
|
49
|
+
return [s[@"CGSSessionScreenIsLocked"] boolValue] &&
|
|
50
|
+
[s[(__bridge NSString *)kCGSessionOnConsoleKey] boolValue] &&
|
|
51
|
+
[s[(__bridge NSString *)kCGSessionLoginDoneKey] boolValue] &&
|
|
52
|
+
[s[(__bridge NSString *)kCGSessionUserIDKey] isKindOfClass:NSNumber.class] &&
|
|
53
|
+
[s[(__bridge NSString *)kCGSessionUserIDKey] unsignedIntValue] == getuid();
|
|
54
|
+
}
|
|
55
|
+
static int saveLegacyLock(CGDirectDisplayID display, NSString *path, CGImageRef (*snapshot)(CGDirectDisplayID)) {
|
|
56
|
+
if (!snapshot || !legacyLockedSession() || !CGPreflightScreenCaptureAccess() || !CGDisplayIsOnline(display)) return 1;
|
|
57
|
+
// Sleeping displays return NULL. An explicit screenshot request may wake
|
|
58
|
+
// the display; the system still owns the lock and validates any password.
|
|
59
|
+
IOPMAssertionID assertion = kIOPMNullAssertionID;
|
|
60
|
+
IOPMAssertionDeclareUserActivity(CFSTR("Octopus desktop screenshot"), kIOPMUserActiveLocal, &assertion);
|
|
61
|
+
NSDate *deadline = [NSDate dateWithTimeIntervalSinceNow:2];
|
|
62
|
+
CGImageRef image = NULL;
|
|
63
|
+
while (deadline.timeIntervalSinceNow > 0 && legacyLockedSession() && CGPreflightScreenCaptureAccess()) {
|
|
64
|
+
image = snapshot(display);
|
|
65
|
+
if (image) break;
|
|
66
|
+
[NSRunLoop.currentRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:.05]];
|
|
67
|
+
}
|
|
68
|
+
BOOL saved = image && legacyLockedSession() && CGPreflightScreenCaptureAccess() && saveImage(image, path);
|
|
69
|
+
if (image) CGImageRelease(image);
|
|
70
|
+
if (assertion != kIOPMNullAssertionID) IOPMAssertionRelease(assertion);
|
|
71
|
+
return saved ? 0 : 1;
|
|
72
|
+
}
|
|
73
|
+
static int captureLegacyLock(CGDirectDisplayID display, NSString *path) {
|
|
74
|
+
if (@available(macOS 14.0, *)) return -1;
|
|
75
|
+
if (!legacyLockedSession()) return -1;
|
|
76
|
+
(void)NSScreen.screens;
|
|
77
|
+
return saveLegacyLock(display, path, dlsym(RTLD_DEFAULT, "CGDisplayCreateImage"));
|
|
78
|
+
}
|
|
79
|
+
|
|
42
80
|
static void finishImage(BOOL saved) {
|
|
43
81
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
44
82
|
if (finished) return;
|
|
@@ -131,6 +169,11 @@ int main(int argc, const char *argv[]) {
|
|
|
131
169
|
}
|
|
132
170
|
DesktopFrame *frame = [DesktopFrame new];
|
|
133
171
|
frame.path = [NSString stringWithUTF8String:argv[3]];
|
|
172
|
+
int legacy = captureLegacyLock((CGDirectDisplayID)requested, frame.path);
|
|
173
|
+
if (legacy >= 0) {
|
|
174
|
+
if (legacy) fail(@"无法捕获当前用户的锁屏画面,请检查图形会话和屏幕录制权限。" );
|
|
175
|
+
return legacy;
|
|
176
|
+
}
|
|
134
177
|
[SCShareableContent getShareableContentExcludingDesktopWindows:NO onScreenWindowsOnly:YES completionHandler:^(SCShareableContent *content, NSError *error) {
|
|
135
178
|
if (error || !content) { dispatch_async(dispatch_get_main_queue(), ^{ fail(permissionMessage()); }); return; }
|
|
136
179
|
SCDisplay *display = nil;
|
package/native/remote-macos.m
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
#import <ScreenCaptureKit/ScreenCaptureKit.h>
|
|
5
5
|
#import <Carbon/Carbon.h>
|
|
6
6
|
#import <IOKit/pwr_mgt/IOPMLib.h>
|
|
7
|
+
#import <VideoToolbox/VideoToolbox.h>
|
|
8
|
+
#include <dlfcn.h>
|
|
7
9
|
#include <arpa/inet.h>
|
|
8
10
|
#include <fcntl.h>
|
|
9
11
|
#include <signal.h>
|
|
@@ -32,6 +34,11 @@ static BOOL screenLocked(void) {
|
|
|
32
34
|
return [s[@"CGSSessionScreenIsLocked"] boolValue];
|
|
33
35
|
}
|
|
34
36
|
static BOOL inputOK(void) { return AXIsProcessTrusted() && CGPreflightPostEventAccess() && sessionPresent(); }
|
|
37
|
+
typedef CGImageRef (*LegacySnapshot)(CGDirectDisplayID);
|
|
38
|
+
static LegacySnapshot legacySnapshot(void) {
|
|
39
|
+
if (@available(macOS 14.0,*)) return NULL;
|
|
40
|
+
return (LegacySnapshot)dlsym(RTLD_DEFAULT,"CGDisplayCreateImage");
|
|
41
|
+
}
|
|
35
42
|
// Quartz events can fill the password field without marking the console user
|
|
36
43
|
// active. loginwindow then times out its activity check and reports a bad
|
|
37
44
|
// password. Declare activity on connection and real console input, never on
|
|
@@ -122,15 +129,26 @@ static BOOL writeAll(int fd,const void *data,size_t length) {
|
|
|
122
129
|
@property(nonatomic) double snapshotStarted, lastSnapshot;
|
|
123
130
|
- (void)consumeSample:(CMSampleBufferRef)sampleBuffer;
|
|
124
131
|
- (void)captureLockedFrame;
|
|
132
|
+
- (void)captureLegacyLockedFrame;
|
|
133
|
+
- (void)captureLegacyLockedFrameWithSnapshot:(LegacySnapshot)snapshot;
|
|
134
|
+
- (void)releaseLegacyDisplay;
|
|
125
135
|
@property(nonatomic,strong) NSData *pendingFrame;
|
|
126
136
|
@property(nonatomic,strong) dispatch_queue_t writerQueue;
|
|
127
137
|
@property(nonatomic) BOOL writing;
|
|
138
|
+
@property(nonatomic) IOPMAssertionID legacyDisplayAssertion;
|
|
128
139
|
@end
|
|
129
140
|
@implementation RemoteStream
|
|
130
141
|
- (instancetype)init {
|
|
131
142
|
if ((self=[super init])) self.writerQueue=dispatch_queue_create("octopus.remote.writer",DISPATCH_QUEUE_SERIAL);
|
|
132
143
|
return self;
|
|
133
144
|
}
|
|
145
|
+
- (void)dealloc {
|
|
146
|
+
if(_legacyDisplayAssertion!=kIOPMNullAssertionID) IOPMAssertionRelease(_legacyDisplayAssertion);
|
|
147
|
+
}
|
|
148
|
+
- (void)releaseLegacyDisplay {
|
|
149
|
+
if(_legacyDisplayAssertion!=kIOPMNullAssertionID) IOPMAssertionRelease(_legacyDisplayAssertion);
|
|
150
|
+
_legacyDisplayAssertion=kIOPMNullAssertionID;
|
|
151
|
+
}
|
|
134
152
|
- (void)stream:(SCStream *)stream didStopWithError:(NSError *)error {
|
|
135
153
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
136
154
|
self.streamStopped=YES;
|
|
@@ -158,7 +176,60 @@ static BOOL writeAll(int fd,const void *data,size_t length) {
|
|
|
158
176
|
return;
|
|
159
177
|
}
|
|
160
178
|
#endif
|
|
161
|
-
|
|
179
|
+
[self captureLegacyLockedFrame];
|
|
180
|
+
}
|
|
181
|
+
- (void)captureLegacyLockedFrame {
|
|
182
|
+
[self captureLegacyLockedFrameWithSnapshot:legacySnapshot()];
|
|
183
|
+
}
|
|
184
|
+
- (void)captureLegacyLockedFrameWithSnapshot:(LegacySnapshot)snapshot {
|
|
185
|
+
if(!snapshot) { stopping=1; return; }
|
|
186
|
+
double now=NSDate.date.timeIntervalSince1970;
|
|
187
|
+
if(!self.configuration || now-self.lastSnapshot<fmax(.2,CMTimeGetSeconds(self.configuration.minimumFrameInterval))) return;
|
|
188
|
+
self.lastSnapshot=now;
|
|
189
|
+
if(stopping || !inputOK() || !CGPreflightScreenCaptureAccess() || !screenLocked()) return;
|
|
190
|
+
// Ventura's loginwindow sleeps the display ~30 s after connection, at which
|
|
191
|
+
// point Quartz returns NULL. Keep it visible only for this remote session.
|
|
192
|
+
// This power assertion does not generate input or dismiss the system lock.
|
|
193
|
+
if(self.legacyDisplayAssertion==kIOPMNullAssertionID &&
|
|
194
|
+
IOPMAssertionCreateWithName(kIOPMAssertionTypePreventUserIdleDisplaySleep,kIOPMAssertionLevelOn,
|
|
195
|
+
CFSTR("Octopus locked remote display"),&_legacyDisplayAssertion)!=kIOReturnSuccess) { stopping=1; return; }
|
|
196
|
+
CGImageRef image=snapshot(displayID);
|
|
197
|
+
if(!image) {
|
|
198
|
+
if(!self.snapshotStarted) self.snapshotStarted=now;
|
|
199
|
+
if(now-self.snapshotStarted>2) stopping=1;
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
self.snapshotStarted=0;
|
|
203
|
+
size_t w=self.configuration.width,h=self.configuration.height;
|
|
204
|
+
if(!w || !h || w>3840 || h>2160 || w%2 || h%2) { CGImageRelease(image); stopping=1; return; }
|
|
205
|
+
CVPixelBufferRef rgb=NULL,yuv=NULL;
|
|
206
|
+
VTPixelTransferSessionRef transfer=NULL;
|
|
207
|
+
CGContextRef context=NULL;
|
|
208
|
+
CGColorSpaceRef color=CGColorSpaceCreateDeviceRGB();
|
|
209
|
+
BOOL converted=NO;
|
|
210
|
+
if(CVPixelBufferCreate(NULL,w,h,kCVPixelFormatType_32BGRA,NULL,&rgb)==kCVReturnSuccess &&
|
|
211
|
+
CVPixelBufferCreate(NULL,w,h,kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange,NULL,&yuv)==kCVReturnSuccess) {
|
|
212
|
+
CVPixelBufferLockBaseAddress(rgb,0);
|
|
213
|
+
context=CGBitmapContextCreate(CVPixelBufferGetBaseAddress(rgb),w,h,8,CVPixelBufferGetBytesPerRow(rgb),color,kCGImageAlphaNoneSkipFirst|kCGBitmapByteOrder32Little);
|
|
214
|
+
if(context) CGContextDrawImage(context,CGRectMake(0,0,w,h),image);
|
|
215
|
+
CVPixelBufferUnlockBaseAddress(rgb,0);
|
|
216
|
+
if(context && VTPixelTransferSessionCreate(NULL,&transfer)==noErr)
|
|
217
|
+
converted=VTPixelTransferSessionTransferImage(transfer,rgb,yuv)==noErr;
|
|
218
|
+
}
|
|
219
|
+
if(converted && !stopping && inputOK() && CGPreflightScreenCaptureAccess() && screenLocked()) {
|
|
220
|
+
CMVideoFormatDescriptionRef format=NULL; CMSampleBufferRef sample=NULL;
|
|
221
|
+
CMSampleTimingInfo timing={kCMTimeInvalid,kCMTimeZero,kCMTimeInvalid};
|
|
222
|
+
if(CMVideoFormatDescriptionCreateForImageBuffer(NULL,yuv,&format)==noErr &&
|
|
223
|
+
CMSampleBufferCreateReadyWithImageBuffer(NULL,yuv,format,&timing,&sample)==noErr) [self consumeSample:sample];
|
|
224
|
+
else stopping=1;
|
|
225
|
+
if(sample) CFRelease(sample);
|
|
226
|
+
if(format) CFRelease(format);
|
|
227
|
+
} else if(!converted) stopping=1;
|
|
228
|
+
if(context) CGContextRelease(context);
|
|
229
|
+
if(transfer) CFRelease(transfer);
|
|
230
|
+
if(rgb) CFRelease(rgb);
|
|
231
|
+
if(yuv) CFRelease(yuv);
|
|
232
|
+
CGColorSpaceRelease(color); CGImageRelease(image);
|
|
162
233
|
}
|
|
163
234
|
- (void)stream:(SCStream *)stream didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer ofType:(SCStreamOutputType)type {
|
|
164
235
|
if (stopping || type!=SCStreamOutputTypeScreen || !CMSampleBufferIsValid(sampleBuffer)) return;
|
|
@@ -264,7 +335,7 @@ int main(int argc,const char *argv[]) {
|
|
|
264
335
|
@autoreleasepool {
|
|
265
336
|
if (@available(macOS 12.3,*)) {
|
|
266
337
|
if(argc==2 && strcmp(argv[1],"check")==0) {
|
|
267
|
-
BOOL lockCapture=
|
|
338
|
+
BOOL lockCapture=legacySnapshot()!=NULL;
|
|
268
339
|
#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 140000
|
|
269
340
|
if(@available(macOS 14.0,*)) lockCapture=YES;
|
|
270
341
|
#endif
|
|
@@ -313,7 +384,9 @@ int main(int argc,const char *argv[]) {
|
|
|
313
384
|
while(!stopping) {
|
|
314
385
|
@autoreleasepool {
|
|
315
386
|
if(getppid()!=parentPID || NSDate.date.timeIntervalSince1970-lastPing>15 || !inputOK() || !CGPreflightScreenCaptureAccess() || !CGDisplayIsOnline(displayID) || !CGRectEqualToRect(bounds,CGDisplayBounds(displayID))) break;
|
|
316
|
-
|
|
387
|
+
BOOL locked=screenLocked();
|
|
388
|
+
if(!locked && capture.legacyDisplayAssertion!=kIOPMNullAssertionID) [capture releaseLegacyDisplay];
|
|
389
|
+
if(locked) [capture captureLockedFrame];
|
|
317
390
|
else if(capture.streamStopped && capture.stream) {
|
|
318
391
|
capture.streamStopped=NO;
|
|
319
392
|
[capture.stream startCaptureWithCompletionHandler:^(NSError *e) { if(e) stopping=1; }];
|
|
@@ -330,7 +403,7 @@ int main(int argc,const char *argv[]) {
|
|
|
330
403
|
[NSRunLoop.currentRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:.005]];
|
|
331
404
|
}
|
|
332
405
|
}
|
|
333
|
-
stopping=1; releaseAll(); releaseActivity(); [capture.stream stopCaptureWithCompletionHandler:nil]; return 0;
|
|
406
|
+
stopping=1; releaseAll(); releaseActivity(); [capture releaseLegacyDisplay]; [capture.stream stopCaptureWithCompletionHandler:nil]; return 0;
|
|
334
407
|
}
|
|
335
408
|
return 1;
|
|
336
409
|
}
|