Remove timer-based polling, always use CADisplayLink for frame updates

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 <noreply@anthropic.com>
This commit is contained in:
Mousen
2025-12-08 07:03:17 +05:00
parent 404b9ace35
commit aecc88aed2
5 changed files with 115 additions and 80 deletions

86
screendump/CLAUDE.md Normal file
View File

@@ -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=<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

View File

@@ -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;

View File

@@ -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,18 +65,6 @@
return;
}
bool updateFrame = true;
if (!_useCADisplayLink) {
bool updateFrame = false;
int32_t currentFrameSeed = IOSurfaceGetSeed(_screenSurface);
if (_lastUpdatedSeed != currentFrameSeed && rfbIsActive(_rfbScreenInfo)) {
_lastUpdatedSeed = currentFrameSeed;
updateFrame = true;
}
};
if (updateFrame) {
// dispatch_async(dispatch_get_main_queue(), ^{
[_q addOperationWithBlock: ^{
// Get current capture mode
CaptureMode mode = _captureModeBlock ? _captureModeBlock() : CaptureModeCARenderServer;
@@ -105,16 +88,14 @@
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;
});
}
@@ -123,17 +104,10 @@
[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];
});
}
_displayLink = displayLink;
}
-(void)dealloc {

View File

@@ -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;

View File

@@ -81,26 +81,6 @@
<key>isNumeric</key>
<true/>
</dict>
<dict>
<key>cell</key>
<string>PSGroupCell</string>
<key>label</key>
<string>Tuning</string>
</dict>
<dict>
<key>PostNotification</key>
<string>com.mousen.screendump/restart</string>
<key>cell</key>
<string>PSSwitchCell</string>
<key>default</key>
<false/>
<key>defaults</key>
<string>com.mousen.screendump</string>
<key>key</key>
<string>displaysync</string>
<key>label</key>
<string>Update screen using CADisplayLink</string>
</dict>
</array>
</dict>
</plist>