From aecc88aed2d68c227d17ec4b6eacee46b7491990 Mon Sep 17 00:00:00 2001 From: Mousen Date: Mon, 8 Dec 2025 07:03:17 +0500 Subject: [PATCH] Remove timer-based polling, always use CADisplayLink for frame updates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CADisplayLink provides better vsync alignment and consistent 60fps updates. The timer-based 400Hz polling path added unnecessary complexity and CPU overhead without meaningful benefits. - Remove useCADisplayLink parameter and displaysync preference - Simplify FrameUpdater to always use CADisplayLink - Remove preference UI toggle for display sync option - Add CLAUDE.md project documentation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- screendump/CLAUDE.md | 86 +++++++++++++++++++ screendump/FrameUpdater.h | 1 - screendump/FrameUpdater.m | 84 +++++++----------- screendump/ScreenDumpVNC.m | 4 - .../Preferences/screendump/Preferences.plist | 20 ----- 5 files changed, 115 insertions(+), 80 deletions(-) create mode 100644 screendump/CLAUDE.md diff --git a/screendump/CLAUDE.md b/screendump/CLAUDE.md new file mode 100644 index 0000000..877ca06 --- /dev/null +++ b/screendump/CLAUDE.md @@ -0,0 +1,86 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +ScreenDump is a VNC server for jailbroken iOS 15+ devices (rootless/Ellekit). It captures the device screen and streams it via VNC protocol while supporting remote keyboard and touch input. + +## Build Commands + +```bash +# Build the package (requires Theos installed) +make package + +# Clean build artifacts +make clean + +# Build and install to device (requires SSH access) +make package install THEOS_DEVICE_IP= +``` + +The build produces `screendumpd` daemon installed to `/usr/libexec`. + +## Architecture + +### Core Components + +- **ScreenDumpVNC** ([ScreenDumpVNC.m](ScreenDumpVNC.m)) - Main singleton controller that: + - Initializes libvncserver with screen dimensions + - Manages IOSurface framebuffer acquisition via IOMobileFramebuffer + - Creates IOSurfaceAccelerator for frame scaling/transfer + - Handles dual capture mode switching + +- **FrameUpdater** ([FrameUpdater.m](FrameUpdater.m)) - Frame capture loop: + - Runs on NSOperationQueue with max 1 concurrent operation (serialized) + - Uses CADisplayLink for vsync-based updates at 60fps + - Transfers frames via IOSurfaceAcceleratorTransferSurface or CARenderServerRenderDisplay + +- **VNC Event Handlers** ([vnc.m](vnc.m)) - Input injection: + - Keyboard events via IOHIDEventSystemClient (XK keysym to HID usage mapping) + - Touch/pointer via IOHIDDigitizer events (normalized coordinates) + - Backslash key (`\`) toggles capture mode + +### Capture Modes + +Two capture modes available, toggled via backslash key during VNC session: + +1. **CARenderServer** (default) - Uses `CARenderServerRenderDisplay()`, faster but hides secure text fields +2. **IOMobileFramebuffer** (legacy) - Direct framebuffer access, shows password fields but auto-reverts after 10 seconds + +### Frame Pipeline + +``` +IOMobileFramebuffer → screenSurface (native res) + ↓ + [CARenderServer or direct transfer] + ↓ + renderServerSurface (if scaling needed) + ↓ + IOSurfaceAccelerator transfer + ↓ + staticBuffer (VNC output res) + ↓ + rfbMarkRectAsModified → VNC clients +``` + +### Dependencies + +Pre-built libraries in `vncbuild/`: +- libvncserver (VNC protocol) +- libpng, libjpeg (image encoding) +- libssl, libcrypto (VNC authentication) + +## Configuration + +User preferences read from `com.mousen.screendump` preference domain: +- `enabled` - Enable/disable daemon +- `width`, `height` - Custom output resolution (maintains aspect ratio, aligns to 4px) +- `password` - VNC authentication password + +## Entitlements + +[en.plist](en.plist) contains required entitlements for: +- IOSurface/IOMobileFramebuffer access +- HID event dispatch (input injection) +- QuartzCore global capture diff --git a/screendump/FrameUpdater.h b/screendump/FrameUpdater.h index 63543f8..80016cf 100644 --- a/screendump/FrameUpdater.h +++ b/screendump/FrameUpdater.h @@ -13,7 +13,6 @@ height:(size_t)height nativeWidth:(size_t)nativeWidth nativeHeight:(size_t)nativeHeight - useCADisplayLink:(BOOL)useCADisplayLink renderServerSurface:(IOSurfaceRef)renderServerSurface captureModeBlock:(CaptureMode(^)(void))captureModeBlock; - (void)startFrameLoop; diff --git a/screendump/FrameUpdater.m b/screendump/FrameUpdater.m index 2063dbc..f81b864 100644 --- a/screendump/FrameUpdater.m +++ b/screendump/FrameUpdater.m @@ -5,8 +5,7 @@ // Process NSOperationQueue *_q; BOOL _updatingFrames; - uint32_t _lastUpdatedSeed; - NSTimer* _updateFrameTimer; + CADisplayLink *_displayLink; // Shared from ScreenDumpVNC IOSurfaceRef _screenSurface; @@ -17,7 +16,6 @@ size_t _height; size_t _nativeWidth; size_t _nativeHeight; - BOOL _useCADisplayLink; // Dual capture mode support IOSurfaceRef _renderServerSurface; // Separate surface for CARenderServer (avoids framebuffer locking) @@ -32,15 +30,13 @@ height:(size_t)height nativeWidth:(size_t)nativeWidth nativeHeight:(size_t)nativeHeight - useCADisplayLink:(BOOL)useCADisplayLink renderServerSurface:(IOSurfaceRef)renderServerSurface captureModeBlock:(CaptureMode(^)(void))captureModeBlock { if ((self = [super init])) { _q = [[NSOperationQueue alloc] init]; _q.maxConcurrentOperationCount = 1; // Serialize to prevent frame queue buildup _updatingFrames = NO; - _lastUpdatedSeed = 0; - _updateFrameTimer = nil; + _displayLink = nil; _screenSurface = screenSurface; _rfbScreenInfo = rfbScreenInfo; @@ -50,7 +46,6 @@ _height = height; _nativeWidth = nativeWidth; _nativeHeight = nativeHeight; - _useCADisplayLink = useCADisplayLink; _renderServerSurface = renderServerSurface; _captureModeBlock = [captureModeBlock copy]; @@ -70,51 +65,37 @@ return; } - bool updateFrame = true; - if (!_useCADisplayLink) { - bool updateFrame = false; - int32_t currentFrameSeed = IOSurfaceGetSeed(_screenSurface); - if (_lastUpdatedSeed != currentFrameSeed && rfbIsActive(_rfbScreenInfo)) { - _lastUpdatedSeed = currentFrameSeed; - updateFrame = true; - } - }; + [_q addOperationWithBlock: ^{ + // Get current capture mode + CaptureMode mode = _captureModeBlock ? _captureModeBlock() : CaptureModeCARenderServer; + BOOL needsScaling = (_width != _nativeWidth || _height != _nativeHeight); - if (updateFrame) { - // dispatch_async(dispatch_get_main_queue(), ^{ - [_q addOperationWithBlock: ^{ - // Get current capture mode - CaptureMode mode = _captureModeBlock ? _captureModeBlock() : CaptureModeCARenderServer; - BOOL needsScaling = (_width != _nativeWidth || _height != _nativeHeight); - - if (mode == CaptureModeIOMobileFramebuffer) { - // Legacy mode: Use IOMobileFramebuffer surface transfer (shows passwords) - IOSurfaceAcceleratorTransferSurface(_accelerator, _screenSurface, _staticBuffer, NULL, NULL, NULL, NULL); + if (mode == CaptureModeIOMobileFramebuffer) { + // Legacy mode: Use IOMobileFramebuffer surface transfer (shows passwords) + IOSurfaceAcceleratorTransferSurface(_accelerator, _screenSurface, _staticBuffer, NULL, NULL, NULL, NULL); + } else { + // Default mode: Use CARenderServer (faster, no secure fields) + // Uses separate surface to avoid touching the live framebuffer + if (needsScaling && _renderServerSurface) { + // Capture to our own surface at native resolution, then scale + CARenderServerRenderDisplay(0, CFSTR("LCD"), _renderServerSurface, 0, 0); + IOSurfaceAcceleratorTransferSurface(_accelerator, _renderServerSurface, _staticBuffer, NULL, NULL, NULL, NULL); } else { - // Default mode: Use CARenderServer (faster, no secure fields) - // Uses separate surface to avoid touching the live framebuffer - if (needsScaling && _renderServerSurface) { - // Capture to our own surface at native resolution, then scale - CARenderServerRenderDisplay(0, CFSTR("LCD"), _renderServerSurface, 0, 0); - IOSurfaceAcceleratorTransferSurface(_accelerator, _renderServerSurface, _staticBuffer, NULL, NULL, NULL, NULL); - } else { - // No scaling needed - capture directly to output buffer - CARenderServerRenderDisplay(0, CFSTR("LCD"), _staticBuffer, 0, 0); - } + // No scaling needed - capture directly to output buffer + CARenderServerRenderDisplay(0, CFSTR("LCD"), _staticBuffer, 0, 0); } + } - rfbMarkRectAsModified(_rfbScreenInfo, 0, 0, _width, _height); - }]; - // }); - } + rfbMarkRectAsModified(_rfbScreenInfo, 0, 0, _width, _height); + }]; } -(void)stopFrameLoop { - if (_updateFrameTimer == nil || ![_updateFrameTimer isValid]) return; + if (_displayLink == nil) return; dispatch_async(dispatch_get_main_queue(), ^(void){ - [_updateFrameTimer invalidate]; - _updateFrameTimer = nil; + [_displayLink invalidate]; + _displayLink = nil; _updatingFrames = NO; }); } @@ -122,18 +103,11 @@ -(void)startFrameLoop { [self stopFrameLoop]; _updatingFrames = YES; - - if (_useCADisplayLink) { - CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(_updateFrame)]; - displayLink.preferredFramesPerSecond = 60; // Limit to 60 FPS - [displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode]; - _updateFrameTimer = (NSTimer *)displayLink; - } else { - dispatch_async(dispatch_get_main_queue(), ^(void){ - // Reduced from 1/400 (400 Hz) to 1/60 (60 Hz) to reduce overhead - _updateFrameTimer = [NSTimer scheduledTimerWithTimeInterval:1.0/400.0 target:self selector:@selector(_updateFrame) userInfo:nil repeats:YES]; - }); - } + + CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(_updateFrame)]; + displayLink.preferredFramesPerSecond = 60; // Limit to 60 FPS + [displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode]; + _displayLink = displayLink; } -(void)dealloc { diff --git a/screendump/ScreenDumpVNC.m b/screendump/ScreenDumpVNC.m index 8bf8d0b..8b25594 100644 --- a/screendump/ScreenDumpVNC.m +++ b/screendump/ScreenDumpVNC.m @@ -286,9 +286,6 @@ free(arg0); - NSDictionary* defaults = getPrefsForAppId(@"com.mousen.screendump"); - bool useCADisplayLink = [[defaults objectForKey:@"displaysync"]?:@NO boolValue]; - __weak ScreenDumpVNC *weakSelf = self; _frameUpdater = [[FrameUpdater alloc] initWithSurfaceInfo:_screenSurface rfbScreenInfo:_rfbScreenInfo @@ -298,7 +295,6 @@ height:_height nativeWidth:_nativeWidth nativeHeight:_nativeHeight - useCADisplayLink:useCADisplayLink renderServerSurface:_renderServerSurface captureModeBlock:^CaptureMode(void) { ScreenDumpVNC *strongSelf = weakSelf; diff --git a/screendump/layout/Library/PreferenceLoader/Preferences/screendump/Preferences.plist b/screendump/layout/Library/PreferenceLoader/Preferences/screendump/Preferences.plist index 1ab6d37..518421f 100644 --- a/screendump/layout/Library/PreferenceLoader/Preferences/screendump/Preferences.plist +++ b/screendump/layout/Library/PreferenceLoader/Preferences/screendump/Preferences.plist @@ -81,26 +81,6 @@ isNumeric - - cell - PSGroupCell - label - Tuning - - - PostNotification - com.mousen.screendump/restart - cell - PSSwitchCell - default - - defaults - com.mousen.screendump - key - displaysync - label - Update screen using CADisplayLink -