From 297e88fa13bd83c7f65c642ec36c495bec5af69f Mon Sep 17 00:00:00 2001 From: Mousen Date: Mon, 8 Dec 2025 05:30:19 +0500 Subject: [PATCH] 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. --- screendump/FrameUpdater.h | 11 +- screendump/FrameUpdater.m | 32 ++++- screendump/IOMobileFramebuffer.h | 11 +- screendump/Makefile | 2 +- screendump/ScreenDumpVNC.h | 7 ++ screendump/ScreenDumpVNC.m | 200 ++++++++++++++++++++++++++++++- screendump/utils.m | 2 +- screendump/vnc.m | 8 ++ 8 files changed, 262 insertions(+), 11 deletions(-) diff --git a/screendump/FrameUpdater.h b/screendump/FrameUpdater.h index 7993b6b..1da8323 100644 --- a/screendump/FrameUpdater.h +++ b/screendump/FrameUpdater.h @@ -2,9 +2,18 @@ #import #import #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 diff --git a/screendump/FrameUpdater.m b/screendump/FrameUpdater.m index 73685de..9dbfe75 100644 --- a/screendump/FrameUpdater.m +++ b/screendump/FrameUpdater.m @@ -1,7 +1,7 @@ #import "FrameUpdater.h" @implementation FrameUpdater { - + // Process NSOperationQueue *_q; BOOL _updatingFrames; @@ -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: ^{ - IOSurfaceAcceleratorTransferSurface(_accelerator, _screenSurface, _staticBuffer, NULL, NULL, NULL, NULL); + // 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); }]; // }); diff --git a/screendump/IOMobileFramebuffer.h b/screendump/IOMobileFramebuffer.h index 79d56dd..7f048ef 100644 --- a/screendump/IOMobileFramebuffer.h +++ b/screendump/IOMobileFramebuffer.h @@ -34,4 +34,13 @@ extern IOMobileFramebufferReturn IOMobileFramebufferGetLayerDefaultSurface(IOMob extern IOMobileFramebufferReturn IOMobileFramebufferCopyLayerDisplayedSurface(IOMobileFramebufferRef pointer, int surface, IOSurfaceRef *buffer); 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(); \ No newline at end of file +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 +); \ No newline at end of file diff --git a/screendump/Makefile b/screendump/Makefile index 64c4cbd..3fa56e1 100644 --- a/screendump/Makefile +++ b/screendump/Makefile @@ -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" diff --git a/screendump/ScreenDumpVNC.h b/screendump/ScreenDumpVNC.h index 0d45de8..9e79d85 100644 --- a/screendump/ScreenDumpVNC.h +++ b/screendump/ScreenDumpVNC.h @@ -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 \ No newline at end of file diff --git a/screendump/ScreenDumpVNC.m b/screendump/ScreenDumpVNC.m index 43118c2..9a99fb2 100644 --- a/screendump/ScreenDumpVNC.m +++ b/screendump/ScreenDumpVNC.m @@ -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 diff --git a/screendump/utils.m b/screendump/utils.m index 48735a7..e9c876b 100644 --- a/screendump/utils.m +++ b/screendump/utils.m @@ -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; diff --git a/screendump/vnc.m b/screendump/vnc.m index e8f544a..f92027f 100644 --- a/screendump/vnc.m +++ b/screendump/vnc.m @@ -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); }