Add dual capture mode support and aspect ratio adjustments

- Introduced CaptureMode enum for selecting between CARenderServer and IOMobileFramebuffer.
- Updated FrameUpdater and ScreenDumpVNC to handle new capture mode logic.
- Implemented aspect ratio calculations and adjustments in ScreenDumpVNC.
- Added toggleCaptureMode method to switch between capture modes via keyboard input.
- Enhanced initialization of FrameUpdater to accommodate new parameters.
This commit is contained in:
Mousen
2025-12-08 05:30:19 +05:00
parent 9757a0c623
commit 297e88fa13
8 changed files with 262 additions and 11 deletions

View File

@@ -2,9 +2,18 @@
#import <UIKit/UIKit.h>
#import <rfb/rfb.h>
#import "IOMobileFramebuffer.h"
#import "ScreenDumpVNC.h"
@interface FrameUpdater : NSObject
-(instancetype)initWithSurfaceInfo:(IOSurfaceRef)screenSurface rfbScreenInfo:(rfbScreenInfoPtr)rfbScreenInfo accelerator:(IOSurfaceAcceleratorRef)accelerator staticBuffer:(IOSurfaceRef)staticBuffer width:(size_t)width height:(size_t)height;
-(instancetype)initWithSurfaceInfo:(IOSurfaceRef)screenSurface
rfbScreenInfo:(rfbScreenInfoPtr)rfbScreenInfo
accelerator:(IOSurfaceAcceleratorRef)accelerator
staticBuffer:(IOSurfaceRef)staticBuffer
width:(size_t)width
height:(size_t)height
useCADisplayLink:(BOOL)useCADisplayLink
renderServerSurface:(IOSurfaceRef)renderServerSurface
captureModeBlock:(CaptureMode(^)(void))captureModeBlock;
- (void)startFrameLoop;
- (void)stopFrameLoop;
@end

View File

@@ -16,9 +16,21 @@
size_t _width;
size_t _height;
BOOL _useCADisplayLink;
// Dual capture mode support
IOSurfaceRef _renderServerSurface;
CaptureMode(^_captureModeBlock)(void);
}
-(instancetype)initWithSurfaceInfo:(IOSurfaceRef)screenSurface rfbScreenInfo:(rfbScreenInfoPtr)rfbScreenInfo accelerator:(IOSurfaceAcceleratorRef)accelerator staticBuffer:(IOSurfaceRef)staticBuffer width:(size_t)width height:(size_t)height useCADisplayLink:(BOOL)useCADisplayLink {
-(instancetype)initWithSurfaceInfo:(IOSurfaceRef)screenSurface
rfbScreenInfo:(rfbScreenInfoPtr)rfbScreenInfo
accelerator:(IOSurfaceAcceleratorRef)accelerator
staticBuffer:(IOSurfaceRef)staticBuffer
width:(size_t)width
height:(size_t)height
useCADisplayLink:(BOOL)useCADisplayLink
renderServerSurface:(IOSurfaceRef)renderServerSurface
captureModeBlock:(CaptureMode(^)(void))captureModeBlock {
if ((self = [super init])) {
_q = [[NSOperationQueue alloc] init];
_updatingFrames = NO;
@@ -32,6 +44,9 @@
_width = width;
_height = height;
_useCADisplayLink = useCADisplayLink;
_renderServerSurface = renderServerSurface;
_captureModeBlock = [captureModeBlock copy];
}
return self;
}
@@ -55,7 +70,18 @@
if (updateFrame) {
// dispatch_async(dispatch_get_main_queue(), ^{
[_q addOperationWithBlock: ^{
// Get current capture mode
CaptureMode mode = _captureModeBlock ? _captureModeBlock() : CaptureModeCARenderServer;
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)
CARenderServerRenderDisplay(0, CFSTR("LCD"), _renderServerSurface, 0, 0);
IOSurfaceAcceleratorTransferSurface(_accelerator, _renderServerSurface, _staticBuffer, NULL, NULL, NULL, NULL);
}
rfbMarkRectAsModified(_rfbScreenInfo, 0, 0, _width, _height);
}];
// });

View File

@@ -35,3 +35,12 @@ extern IOMobileFramebufferReturn IOMobileFramebufferCopyLayerDisplayedSurface(IO
extern IOMobileFramebufferReturn IOMobileFramebufferOpen(IOMobileFramebufferService service, mach_port_t owningTask, unsigned int type, IOMobileFramebufferRef *pointer);
extern IOMobileFramebufferReturn IOMobileFramebufferGetMainDisplay(IOMobileFramebufferRef *pointer);
extern mach_port_t mach_task_self();
// CARenderServer for screen capture (faster, no secure fields)
extern kern_return_t CARenderServerRenderDisplay(
uint32_t displayId,
CFStringRef displayName,
IOSurfaceRef surface,
uint32_t x,
uint32_t y
);

View File

@@ -10,7 +10,7 @@ TOOL_NAME = screendumpd
$(TOOL_NAME)_FILES = $(wildcard *.m)
$(TOOL_NAME)_FRAMEWORKS := IOSurface IOKit
$(TOOL_NAME)_PRIVATE_FRAMEWORKS := IOMobileFramebuffer IOSurface
$(TOOL_NAME)_OBJCFLAGS += -I./vncbuild/include -Iinclude
$(TOOL_NAME)_OBJCFLAGS += -I./vncbuild/include -Iinclude -fobjc-arc
$(TOOL_NAME)_LDFLAGS += -Wl,-segalign,4000 -L./vncbuild/lib -lvncserver -lpng -llzo2 -ljpeg -lssl -lcrypto -lz
$(TOOL_NAME)_CFLAGS = -w
$(TOOL_NAME)_CODESIGN_FLAGS = "-Sen.plist"

View File

@@ -3,10 +3,17 @@
#define kVNCServerName "ScreenDumpVNC"
typedef enum {
CaptureModeCARenderServer = 0, // Default: faster, no secure fields
CaptureModeIOMobileFramebuffer = 1 // Legacy: shows passwords
} CaptureMode;
@interface ScreenDumpVNC : NSObject
+(void)load;
+(instancetype)sharedInstance;
-(rfbBool)handleVNCAuthorization:(rfbClientPtr)client data:(const char *)data size:(int)size;
-(size_t)width;
-(size_t)height;
-(void)toggleCaptureMode;
-(CaptureMode)currentCaptureMode;
@end

View File

@@ -14,6 +14,11 @@
rfbScreenInfoPtr _rfbScreenInfo;
bool _vncIsRunning;
// Aspect ratio tracking
double _displayAspectRatio; // width/height of native display
size_t _nativeWidth; // IOSurface native width
size_t _nativeHeight; // IOSurface native height
// sent to FrameUpdater
IOSurfaceRef _screenSurface;
size_t _sizeImage;
@@ -23,6 +28,11 @@
size_t _height;
FrameUpdater *_frameUpdater;
// Dual capture mode support
CaptureMode _captureMode;
NSTimer *_legacyModeTimer;
IOSurfaceRef _renderServerSurface;
}
+(void)load {
@@ -67,7 +77,7 @@
_rfbScreenInfo->authPasswdData = nil;
if (_password && _password.length) {
_rfbScreenInfo->authPasswdData = (void *)_password;
_rfbScreenInfo->authPasswdData = (__bridge void *)_password;
}
}
@@ -88,6 +98,83 @@
rfbShutdownServer(_rfbScreenInfo, YES);
}
-(void)_calculateDisplayAspectRatio {
_nativeWidth = IOSurfaceGetWidth(_screenSurface);
_nativeHeight = IOSurfaceGetHeight(_screenSurface);
// Validate to prevent division by zero
if (_nativeHeight == 0 || _nativeWidth == 0) {
NSLog(@"[ScreenDumpVNC] ERROR: Invalid native dimensions, defaulting to 16:9");
_displayAspectRatio = 16.0 / 9.0;
if (_nativeHeight == 0) _nativeHeight = 1;
if (_nativeWidth == 0) _nativeWidth = 1;
return;
}
_displayAspectRatio = (double)_nativeWidth / (double)_nativeHeight;
NSLog(@"[ScreenDumpVNC] Native display: %zux%zu, aspect ratio: %.6f",
_nativeWidth, _nativeHeight, _displayAspectRatio);
}
-(void)_adjustResolutionForAspectRatio {
// Only adjust if custom preferences are set
if (_prefsWidth == 0 || _prefsHeight == 0) {
NSLog(@"[ScreenDumpVNC] No custom dimensions set, using native resolution");
return;
}
// Validate custom preferences
if (_prefsWidth <= 0 || _prefsHeight <= 0) {
NSLog(@"[ScreenDumpVNC] ERROR: Invalid custom dimensions (%dx%d), ignoring",
_prefsWidth, _prefsHeight);
_prefsWidth = 0;
_prefsHeight = 0;
return;
}
// Calculate ideal dimensions maintaining aspect ratio
double idealHeight = (double)_prefsWidth / _displayAspectRatio;
double idealWidth = (double)_prefsHeight * _displayAspectRatio;
size_t adjustedWidth, adjustedHeight;
// Choose option that maximizes pixels while staying within BOTH constraints
if (idealHeight <= (double)_prefsHeight) {
// Use full width, adjust height down
adjustedWidth = _prefsWidth;
adjustedHeight = (size_t)floor(idealHeight);
} else {
// Use full height, adjust width down
adjustedWidth = (size_t)floor(idealWidth);
adjustedHeight = _prefsHeight;
}
// Round width down to nearest multiple of 4 (VNC alignment requirement)
adjustedWidth = (adjustedWidth / 4) * 4;
// Recalculate height based on aligned width to maintain exact aspect ratio
if (adjustedWidth > 0) {
adjustedHeight = (size_t)floor((double)adjustedWidth / _displayAspectRatio);
}
// Round height down to nearest multiple of 4 for consistency
adjustedHeight = (adjustedHeight / 4) * 4;
// Ensure minimum dimensions (4x4 to satisfy alignment requirements)
if (adjustedWidth < 4) adjustedWidth = 4;
if (adjustedHeight < 4) adjustedHeight = 4;
// Log adjustment if changed
if (adjustedWidth != _prefsWidth || adjustedHeight != _prefsHeight) {
NSLog(@"[ScreenDumpVNC] Adjusted resolution from %dx%d to %zux%zu to maintain %.6f aspect ratio",
_prefsWidth, _prefsHeight, adjustedWidth, adjustedHeight, _displayAspectRatio);
}
// Update preferences with corrected values
_prefsWidth = (int)adjustedWidth;
_prefsHeight = (int)adjustedHeight;
}
-(void)setupScreenInfo {
size_t bytesPerPixel;
size_t bitsPerSample;
@@ -101,8 +188,15 @@
if (_screenSurface == NULL) IOMobileFramebufferCopyLayerDisplayedSurface(framebufferConnection, 0, &_screenSurface);
_width = _prefsWidth == 0 ? IOSurfaceGetWidth(_screenSurface) : _prefsWidth;
_height = _prefsHeight == 0 ? IOSurfaceGetHeight(_screenSurface) : _prefsHeight;
// Calculate display aspect ratio from native IOSurface dimensions
[self _calculateDisplayAspectRatio];
// Adjust custom preferences to maintain aspect ratio
[self _adjustResolutionForAspectRatio];
// Set final dimensions (preferences are now corrected if custom, or will use native)
_width = _prefsWidth == 0 ? _nativeWidth : _prefsWidth;
_height = _prefsHeight == 0 ? _nativeHeight : _prefsHeight;
_sizeImage = IOSurfaceGetAllocSize(_screenSurface);
// TODO: do these change at all? this might have been done for perf reasons
@@ -122,6 +216,21 @@
[NSNumber numberWithInt:'BGRA'], kIOSurfacePixelFormat,
[NSNumber numberWithInt:(_width*_height*bytesPerPixel)], kIOSurfaceAllocSize,
nil]);
// Create surface for CARenderServer capture (default mode)
_renderServerSurface = IOSurfaceCreate((CFDictionaryRef) [NSDictionary dictionaryWithObjectsAndKeys:
@"PurpleEDRAM", kIOSurfaceMemoryRegion,
[NSNumber numberWithInt:bytesPerPixel*_width], kIOSurfaceBytesPerRow,
[NSNumber numberWithInt:bytesPerPixel], kIOSurfaceBytesPerElement,
[NSNumber numberWithInt:_width], kIOSurfaceWidth,
[NSNumber numberWithInt:_height], kIOSurfaceHeight,
[NSNumber numberWithInt:'BGRA'], kIOSurfacePixelFormat,
[NSNumber numberWithInt:(_width*_height*bytesPerPixel)], kIOSurfaceAllocSize,
nil]);
// Initialize capture mode to CARenderServer (default)
_captureMode = CaptureModeCARenderServer;
_legacyModeTimer = nil;
}
int argc = 1;
@@ -144,7 +253,20 @@
NSDictionary* defaults = getPrefsForAppId(@"com.mousen.screendump");
bool useCADisplayLink = [[defaults objectForKey:@"displaysync"]?:@NO boolValue];
_frameUpdater = [[FrameUpdater alloc] initWithSurfaceInfo:_screenSurface rfbScreenInfo:_rfbScreenInfo accelerator:_accelerator staticBuffer:_staticBuffer width:_width height:_height useCADisplayLink:useCADisplayLink];
__weak ScreenDumpVNC *weakSelf = self;
_frameUpdater = [[FrameUpdater alloc] initWithSurfaceInfo:_screenSurface
rfbScreenInfo:_rfbScreenInfo
accelerator:_accelerator
staticBuffer:_staticBuffer
width:_width
height:_height
useCADisplayLink:useCADisplayLink
renderServerSurface:_renderServerSurface
captureModeBlock:^CaptureMode(void) {
ScreenDumpVNC *strongSelf = weakSelf;
return strongSelf ? strongSelf->_captureMode : CaptureModeCARenderServer;
}];
}
-(rfbBool)handleVNCAuthorization:(rfbClientPtr)client data:(const char *)data size:(int)size {
@@ -172,4 +294,74 @@
return _enabled;
}
-(void)toggleCaptureMode {
if (_captureMode == CaptureModeCARenderServer) {
// Switch to legacy mode
_captureMode = CaptureModeIOMobileFramebuffer;
NSLog(@"[ScreenDumpVNC] Switched to LEGACY capture mode (shows secure fields)");
// Cancel existing timer if any
if (_legacyModeTimer && [_legacyModeTimer isValid]) {
[_legacyModeTimer invalidate];
_legacyModeTimer = nil;
}
// Create new 10-second timer to auto-disable
__weak ScreenDumpVNC *weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
ScreenDumpVNC *strongSelf = weakSelf;
if (strongSelf) {
strongSelf->_legacyModeTimer = [NSTimer scheduledTimerWithTimeInterval:10.0
target:strongSelf
selector:@selector(_disableLegacyMode)
userInfo:nil
repeats:NO];
}
});
} else {
// Already in legacy mode, switch back to default immediately
_captureMode = CaptureModeCARenderServer;
NSLog(@"[ScreenDumpVNC] Switched to DEFAULT capture mode");
// Cancel timer
if (_legacyModeTimer && [_legacyModeTimer isValid]) {
[_legacyModeTimer invalidate];
}
_legacyModeTimer = nil;
}
}
-(void)_disableLegacyMode {
if (_captureMode != CaptureModeIOMobileFramebuffer) {
return; // Already in default mode
}
_captureMode = CaptureModeCARenderServer;
NSLog(@"[ScreenDumpVNC] Auto-disabled legacy mode, returned to DEFAULT");
// Clean up timer
if (_legacyModeTimer && [_legacyModeTimer isValid]) {
[_legacyModeTimer invalidate];
}
_legacyModeTimer = nil;
}
-(CaptureMode)currentCaptureMode {
return _captureMode;
}
-(void)dealloc {
// Clean up timer
if (_legacyModeTimer && [_legacyModeTimer isValid]) {
[_legacyModeTimer invalidate];
_legacyModeTimer = nil;
}
// Clean up render server surface
if (_renderServerSurface) {
CFRelease(_renderServerSurface);
_renderServerSurface = NULL;
}
}
@end

View File

@@ -4,7 +4,7 @@ NSDictionary* getPrefsForAppId(NSString *appID) {
NSDictionary* defaults = nil;
CFArrayRef keyList = CFPreferencesCopyKeyList((CFStringRef)appID, CFSTR("mobile"), kCFPreferencesAnyHost);
if (keyList) {
defaults = (NSDictionary *)CFPreferencesCopyMultiple(keyList, (CFStringRef)appID, CFSTR("mobile"), kCFPreferencesAnyHost) ? : @{};
defaults = CFBridgingRelease(CFPreferencesCopyMultiple(keyList, (CFStringRef)appID, CFSTR("mobile"), kCFPreferencesAnyHost)) ? : @{};
CFRelease(keyList);
}
return defaults;

View File

@@ -187,6 +187,14 @@ void VNCPointer(int buttons, int x, int y, rfbClientPtr client) {
}
void handleVNCKeyboard(rfbBool down, rfbKeySym key, rfbClientPtr client) {
// Check for backslash key press to toggle capture mode
if (down && (key == XK_backslash || key == XK_bar)) {
ScreenDumpVNC *sharedInstance = [ScreenDumpVNC sharedInstance];
[sharedInstance toggleCaptureMode];
// Consume the key - don't send to device
return;
}
VNCKeyboard(down, key, client);
}