@skylandnpm/octopus-proxy 0.1.55 → 0.1.58
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-NJNANA6A.js → chunk-AOBRAQCJ.js} +53 -8
- package/dist/{chunk-XIJKEY4L.js → chunk-GS3Q6EU5.js} +2 -2
- 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
|
@@ -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({
|
|
@@ -8830,6 +8830,33 @@ var SecureChat = class {
|
|
|
8830
8830
|
import { randomUUID as randomUUID8 } from "node:crypto";
|
|
8831
8831
|
import { execFile as execFile3 } from "node:child_process";
|
|
8832
8832
|
import { promisify as promisify3 } from "node:util";
|
|
8833
|
+
|
|
8834
|
+
// src/reasoning.ts
|
|
8835
|
+
function reasoningLevels(value) {
|
|
8836
|
+
if (!Array.isArray(value)) return [];
|
|
8837
|
+
return [...new Set(value.flatMap((entry) => {
|
|
8838
|
+
const level = typeof entry === "string" ? entry : entry && typeof entry === "object" ? entry.value ?? entry.reasoningEffort ?? entry.id : void 0;
|
|
8839
|
+
return typeof level === "string" && level.trim() ? [level.trim()] : [];
|
|
8840
|
+
}))];
|
|
8841
|
+
}
|
|
8842
|
+
function defaultReasoningLevel(levels) {
|
|
8843
|
+
if (levels.includes("medium")) return "medium";
|
|
8844
|
+
const order = ["none", "off", "nothink", "minimal", "low", "medium", "high", "xhigh", "max", "ultra"];
|
|
8845
|
+
const active = levels.filter((level) => !["none", "off", "nothink"].includes(level));
|
|
8846
|
+
const choices = (active.length ? active : levels).slice().sort((a, b) => {
|
|
8847
|
+
const left = order.indexOf(a), right = order.indexOf(b);
|
|
8848
|
+
return left < 0 || right < 0 ? 0 : left - right;
|
|
8849
|
+
});
|
|
8850
|
+
return choices[Math.floor((choices.length - 1) / 2)];
|
|
8851
|
+
}
|
|
8852
|
+
function selectedReasoningLevel(payload, parameter) {
|
|
8853
|
+
if (typeof payload.reasoningLevel !== "string" || !payload.reasoningLevel) return void 0;
|
|
8854
|
+
if (payload.reasoningParameter !== void 0 && payload.reasoningParameter !== parameter)
|
|
8855
|
+
throw new Error(`Reasoning parameter must be ${parameter} for this tool; refresh the model configuration`);
|
|
8856
|
+
return payload.reasoningLevel;
|
|
8857
|
+
}
|
|
8858
|
+
|
|
8859
|
+
// src/proxy-snapshot.ts
|
|
8833
8860
|
var execute2 = promisify3(execFile3);
|
|
8834
8861
|
function record(value) {
|
|
8835
8862
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
@@ -8844,7 +8871,8 @@ function publicModels(value) {
|
|
|
8844
8871
|
for (const modelValue of Object.values(record(provider.models))) {
|
|
8845
8872
|
const model = record(modelValue);
|
|
8846
8873
|
if (typeof model.id !== "string") continue;
|
|
8847
|
-
|
|
8874
|
+
const levels = reasoningLevels(model.reasoningLevels);
|
|
8875
|
+
result.push({ id: `${provider.id}/${model.id}`, name: typeof model.name === "string" ? model.name : model.id, isDefault: model.isDefault === true, ...Array.isArray(model.reasoningLevels) ? { reasoningLevels: levels } : {}, ...typeof model.reasoningParameter === "string" ? { reasoningParameter: model.reasoningParameter } : {} });
|
|
8848
8876
|
if (result.length >= 500) return result;
|
|
8849
8877
|
}
|
|
8850
8878
|
}
|
|
@@ -10764,6 +10792,8 @@ var OpenCodeAdapter = class {
|
|
|
10764
10792
|
const input = payload.model;
|
|
10765
10793
|
model = { providerID: input.providerID, id: input.modelID ?? input.id, ...input.variant ? { variant: input.variant } : {} };
|
|
10766
10794
|
} else throw new Error("Invalid model identifier");
|
|
10795
|
+
const level = selectedReasoningLevel(payload, "model.variant");
|
|
10796
|
+
if (level) model.variant = level;
|
|
10767
10797
|
await this.data(project, "POST", `${base}/model`, { model });
|
|
10768
10798
|
}
|
|
10769
10799
|
async models(project) {
|
|
@@ -10777,7 +10807,7 @@ var OpenCodeAdapter = class {
|
|
|
10777
10807
|
const modelId = model.id ?? model.modelID;
|
|
10778
10808
|
if (typeof model.providerID !== "string" || typeof modelId !== "string") continue;
|
|
10779
10809
|
const provider = providers.get(model.providerID) ?? { id: model.providerID, models: {} };
|
|
10780
|
-
provider.models[modelId] = { ...model, id: modelId, isDefault: configured ? configured === `${model.providerID}/${modelId}` : model.isDefault === true };
|
|
10810
|
+
provider.models[modelId] = { ...model, id: modelId, reasoningParameter: "model.variant", reasoningLevels: reasoningLevels(model.variants), isDefault: configured ? configured === `${model.providerID}/${modelId}` : model.isDefault === true };
|
|
10781
10811
|
providers.set(model.providerID, provider);
|
|
10782
10812
|
}
|
|
10783
10813
|
return redact({ providers: [...providers.values()] });
|
|
@@ -12103,7 +12133,7 @@ var CodexAdapter = class {
|
|
|
12103
12133
|
const config = await rpc.request("config/read", { cwd: await this.workspace.root(project), includeLayers: false }).catch(() => void 0);
|
|
12104
12134
|
const configured = typeof config?.config?.model === "string" ? config.config.model.trim() : void 0;
|
|
12105
12135
|
if (configured && !models.some((entry) => (entry.model ?? entry.id) === configured)) models.push({ id: configured, model: configured, displayName: configured });
|
|
12106
|
-
return { providers: [{ id: "codex", models: Object.fromEntries(models.map((entry) => [entry.model ?? entry.id, { id: entry.model ?? entry.id, name: entry.displayName ?? entry.id, isDefault: configured ? (entry.model ?? entry.id) === configured : entry.isDefault === true }])) }] };
|
|
12136
|
+
return { providers: [{ id: "codex", models: Object.fromEntries(models.map((entry) => [entry.model ?? entry.id, { id: entry.model ?? entry.id, name: entry.displayName ?? entry.id, ...entry.supportedReasoningEfforts ? { reasoningParameter: "effort", reasoningLevels: reasoningLevels(entry.supportedReasoningEfforts) } : {}, isDefault: configured ? (entry.model ?? entry.id) === configured : entry.isDefault === true }])) }] };
|
|
12107
12137
|
}
|
|
12108
12138
|
if (op === "sessions.list") {
|
|
12109
12139
|
const index = await this.index(project);
|
|
@@ -12164,7 +12194,8 @@ var CodexAdapter = class {
|
|
|
12164
12194
|
if (!input.length) throw new Error("Message or attachments required");
|
|
12165
12195
|
const selectedModel = p.model;
|
|
12166
12196
|
const model = selectedModel?.providerID === "codex" && typeof selectedModel.modelID === "string" ? selectedModel.modelID : void 0;
|
|
12167
|
-
const
|
|
12197
|
+
const effort = selectedReasoningLevel(p, "effort");
|
|
12198
|
+
const result = await rpc.request("turn/start", { threadId: id, input, ...model ? { model } : {}, ...effort ? { effort } : {} });
|
|
12168
12199
|
this.activeTurns.set(id, result.turn.id);
|
|
12169
12200
|
return result;
|
|
12170
12201
|
}
|
|
@@ -12411,7 +12442,7 @@ var ZcodeAdapter = class {
|
|
|
12411
12442
|
const providers = /* @__PURE__ */ new Map();
|
|
12412
12443
|
for (const entry of model.available) {
|
|
12413
12444
|
const provider = providers.get(entry.ref.providerId) ?? { id: entry.ref.providerId, name: entry.providerLabel, models: /* @__PURE__ */ Object.create(null) };
|
|
12414
|
-
provider.models[entry.ref.modelId] = { id: entry.ref.modelId, name: entry.label, isDefault: entry.ref.providerId === model.current?.providerId && entry.ref.modelId === model.current?.modelId };
|
|
12445
|
+
provider.models[entry.ref.modelId] = { id: entry.ref.modelId, name: entry.label, reasoningParameter: "model.options.reasoningLevel", reasoningLevels: reasoningLevels(entry.reasoning?.levels), isDefault: entry.ref.providerId === model.current?.providerId && entry.ref.modelId === model.current?.modelId };
|
|
12415
12446
|
providers.set(provider.id, provider);
|
|
12416
12447
|
}
|
|
12417
12448
|
return { providers: [...providers.values()] };
|
|
@@ -12487,7 +12518,18 @@ var ZcodeAdapter = class {
|
|
|
12487
12518
|
}
|
|
12488
12519
|
if (!lines.length) throw new Error("Message or attachments required");
|
|
12489
12520
|
const selected = p.model;
|
|
12490
|
-
if (selected?.providerID && selected.modelID)
|
|
12521
|
+
if (selected?.providerID && selected.modelID) {
|
|
12522
|
+
let level = selectedReasoningLevel(p, "model.options.reasoningLevel");
|
|
12523
|
+
if (!level) {
|
|
12524
|
+
const snapshot = await rpc.request("session/read", { sessionId: id });
|
|
12525
|
+
const entry = snapshot.settings?.model.available.find((entry2) => entry2.ref.providerId === selected.providerID && entry2.ref.modelId === selected.modelID);
|
|
12526
|
+
level = defaultReasoningLevel(reasoningLevels(entry?.reasoning?.levels));
|
|
12527
|
+
}
|
|
12528
|
+
await rpc.request("session/setModel", { sessionId: id, model: { providerId: selected.providerID, modelId: selected.modelID, ...level ? { options: { reasoningLevel: level } } : {} } });
|
|
12529
|
+
} else if (typeof p.reasoningLevel === "string" && p.reasoningLevel) {
|
|
12530
|
+
const level = selectedReasoningLevel(p, "thoughtLevel");
|
|
12531
|
+
await rpc.request("session/setThoughtLevel", { sessionId: id, thoughtLevel: level });
|
|
12532
|
+
}
|
|
12491
12533
|
return await rpc.request("session/send", { sessionId: id, content: lines.join("\n") });
|
|
12492
12534
|
}
|
|
12493
12535
|
if (op === "sessions.abort") {
|
|
@@ -12749,7 +12791,8 @@ var PiAdapter = class {
|
|
|
12749
12791
|
const providers = /* @__PURE__ */ new Map();
|
|
12750
12792
|
for (const model of models) {
|
|
12751
12793
|
const provider = providers.get(model.provider) ?? { id: model.provider, models: /* @__PURE__ */ Object.create(null) };
|
|
12752
|
-
|
|
12794
|
+
const levels = model.reasoning ? ["off", "minimal", "low", "medium", "high", ...["xhigh", "max"].filter((level) => model.thinkingLevelMap?.[level] != null)].filter((level) => model.thinkingLevelMap?.[level] !== null) : [];
|
|
12795
|
+
provider.models[model.id] = { id: model.id, name: model.name ?? model.id, reasoningParameter: "level", reasoningLevels: reasoningLevels(levels), isDefault: state?.model?.provider === model.provider && state.model.id === model.id };
|
|
12753
12796
|
providers.set(model.provider, provider);
|
|
12754
12797
|
}
|
|
12755
12798
|
return { providers: [...providers.values()] };
|
|
@@ -12850,7 +12893,9 @@ var PiAdapter = class {
|
|
|
12850
12893
|
}
|
|
12851
12894
|
if (!lines.length && !images.length) throw new Error("Message or attachments required");
|
|
12852
12895
|
const model = p.model;
|
|
12896
|
+
const level = selectedReasoningLevel(p, "level");
|
|
12853
12897
|
if (model && typeof model.providerID === "string" && typeof model.modelID === "string") await rpc.request("set_model", { provider: model.providerID, modelId: model.modelID });
|
|
12898
|
+
if (level) await rpc.request("set_thinking_level", { level });
|
|
12854
12899
|
const result = await rpc.request("prompt", { message: lines.join("\n"), ...images.length ? { images } : {} });
|
|
12855
12900
|
session.time.updated = Date.now();
|
|
12856
12901
|
if (session.title === "\u65B0\u4EFB\u52A1" && typeof p.text === "string" && p.text.trim()) session.title = p.text.trim().slice(0, 100);
|
|
@@ -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;
|
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-AOBRAQCJ.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-AOBRAQCJ.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
|
}
|