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:
86
screendump/CLAUDE.md
Normal file
86
screendump/CLAUDE.md
Normal 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
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user