@skylandnpm/octopus-proxy 0.1.57 → 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.
@@ -12,7 +12,7 @@ import {
12
12
  probeRemote,
13
13
  remoteSessionBudget,
14
14
  z
15
- } from "./chunk-XIJKEY4L.js";
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({
@@ -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-YFY544PH.js";
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-XIJKEY4L.js";
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-YFY544PH.js";
6
- import "./chunk-XIJKEY4L.js";
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) => {
@@ -14,7 +14,7 @@ import {
14
14
  remoteResolutionTiers,
15
15
  remoteSessionBudget,
16
16
  terminateNative
17
- } from "./chunk-XIJKEY4L.js";
17
+ } from "./chunk-GS3Q6EU5.js";
18
18
 
19
19
  // src/remote-worker.ts
20
20
  import rtc from "@roamhq/wrtc";
@@ -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;
@@ -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
- stopping=1;
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=NO;
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
- if(screenLocked()) [capture captureLockedFrame];
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skylandnpm/octopus-proxy",
3
- "version": "0.1.57",
3
+ "version": "0.1.58",
4
4
  "type": "module",
5
5
  "description": "Octopus host proxy: OpenCode, Codex, ZCode and Pi adapters with service installer CLI",
6
6
  "bin": {