diff --git a/screendump/FrameUpdater.m b/screendump/FrameUpdater.m index f81b864..b12f40a 100644 --- a/screendump/FrameUpdater.m +++ b/screendump/FrameUpdater.m @@ -1,4 +1,5 @@ #import "FrameUpdater.h" +#import @implementation FrameUpdater { @@ -6,6 +7,7 @@ NSOperationQueue *_q; BOOL _updatingFrames; CADisplayLink *_displayLink; + dispatch_source_t _timer; // Shared from ScreenDumpVNC IOSurfaceRef _screenSurface; @@ -20,6 +22,14 @@ // Dual capture mode support IOSurfaceRef _renderServerSurface; // Separate surface for CARenderServer (avoids framebuffer locking) CaptureMode(^_captureModeBlock)(void); + + // Diagnostics + uint64_t _timerFires; + uint64_t _framesRendered; + uint64_t _framesSkipped; + uint64_t _lastStatsTime; + uint64_t _totalCaptureTime; + uint64_t _maxCaptureTime; } -(instancetype)initWithSurfaceInfo:(IOSurfaceRef)screenSurface @@ -35,6 +45,7 @@ if ((self = [super init])) { _q = [[NSOperationQueue alloc] init]; _q.maxConcurrentOperationCount = 1; // Serialize to prevent frame queue buildup + _q.qualityOfService = NSQualityOfServiceUserInteractive; _updatingFrames = NO; _displayLink = nil; @@ -60,12 +71,19 @@ return; } + _timerFires++; + // Skip if queue is busy to prevent frame buildup and reduce lock contention if (_q.operationCount > 0) { + _framesSkipped++; return; } [_q addOperationWithBlock: ^{ + _framesRendered++; + + uint64_t captureStart = mach_absolute_time(); + // Get current capture mode CaptureMode mode = _captureModeBlock ? _captureModeBlock() : CaptureModeCARenderServer; BOOL needsScaling = (_width != _nativeWidth || _height != _nativeHeight); @@ -86,28 +104,78 @@ } } + uint64_t captureEnd = mach_absolute_time(); + uint64_t captureDuration = captureEnd - captureStart; + _totalCaptureTime += captureDuration; + if (captureDuration > _maxCaptureTime) _maxCaptureTime = captureDuration; + rfbMarkRectAsModified(_rfbScreenInfo, 0, 0, _width, _height); + + // Log stats every second + uint64_t now = mach_absolute_time(); + if (_lastStatsTime == 0) _lastStatsTime = now; + + mach_timebase_info_data_t timebase; + mach_timebase_info(&timebase); + uint64_t elapsed_ns = (now - _lastStatsTime) * timebase.numer / timebase.denom; + + if (elapsed_ns >= NSEC_PER_SEC) { + double elapsed_sec = (double)elapsed_ns / NSEC_PER_SEC; + double avgCaptureMs = (_framesRendered > 0) ? + ((double)_totalCaptureTime * timebase.numer / timebase.denom / _framesRendered / 1000000.0) : 0; + double maxCaptureMs = (double)_maxCaptureTime * timebase.numer / timebase.denom / 1000000.0; + + NSLog(@"[FPS] fps=%.1f skip=%.0f%% | capture: avg=%.1fms max=%.1fms", + _framesRendered / elapsed_sec, + (_timerFires > 0) ? (100.0 * _framesSkipped / _timerFires) : 0.0, + avgCaptureMs, maxCaptureMs); + _timerFires = 0; + _framesRendered = 0; + _framesSkipped = 0; + _totalCaptureTime = 0; + _maxCaptureTime = 0; + _lastStatsTime = now; + } }]; } -(void)stopFrameLoop { - if (_displayLink == nil) return; + _updatingFrames = NO; - dispatch_async(dispatch_get_main_queue(), ^(void){ - [_displayLink invalidate]; - _displayLink = nil; - _updatingFrames = NO; - }); + if (_timer) { + dispatch_source_cancel(_timer); + _timer = nil; + } + + if (_displayLink) { + dispatch_async(dispatch_get_main_queue(), ^(void){ + [_displayLink invalidate]; + _displayLink = nil; + }); + } } -(void)startFrameLoop { [self stopFrameLoop]; _updatingFrames = YES; - CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(_updateFrame)]; - displayLink.preferredFramesPerSecond = 60; // Limit to 60 FPS - [displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode]; - _displayLink = displayLink; + // Use high-priority dispatch timer instead of CADisplayLink + // CADisplayLink may be throttled for daemon processes + dispatch_queue_attr_t attr = dispatch_queue_attr_make_with_qos_class( + DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INTERACTIVE, 0); + dispatch_queue_t timerQueue = dispatch_queue_create("com.mousen.screendump.frametimer", attr); + + _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, DISPATCH_TIMER_STRICT, timerQueue); + + // 60 FPS = ~16.67ms interval + uint64_t interval = NSEC_PER_SEC / 60; + dispatch_source_set_timer(_timer, dispatch_time(DISPATCH_TIME_NOW, 0), interval, 0); + + dispatch_source_set_event_handler(_timer, ^{ + [self _updateFrame]; + }); + + dispatch_resume(_timer); } -(void)dealloc { diff --git a/screendump/Makefile b/screendump/Makefile index 3fa56e1..8d2e689 100644 --- a/screendump/Makefile +++ b/screendump/Makefile @@ -2,19 +2,20 @@ export THEOS_PACKAGE_SCHEME = rootless export ARCHS = arm64 export TARGET = iphone:16.5:14.0 export GO_EASY_ON_ME = 1 -export COPYFILE_DISABLE=1 +export COPYFILE_DISABLE = 1 include $(THEOS)/makefiles/common.mk 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 -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" -$(TOOL_NAME)_INSTALL_PATH = /usr/libexec + +screendumpd_FILES = $(wildcard *.m) +screendumpd_FRAMEWORKS = IOSurface IOKit +screendumpd_PRIVATE_FRAMEWORKS = IOMobileFramebuffer IOSurface +screendumpd_OBJCFLAGS = -I./vncbuild/include -Iinclude -fobjc-arc +screendumpd_LDFLAGS = -Wl,-segalign,4000 -L./vncbuild/lib -lvncserver -lpng -llzo2 -ljpeg -lssl -lcrypto -lz +screendumpd_CFLAGS = -w +screendumpd_CODESIGN_FLAGS = -Sen.plist +screendumpd_INSTALL_PATH = /usr/libexec before-stage:: $(ECHO_NOTHING)find . -name '.DS_Store' -type f -delete$(ECHO_END) diff --git a/screendump/ScreenDumpVNC.m b/screendump/ScreenDumpVNC.m index 8b25594..25d8e34 100644 --- a/screendump/ScreenDumpVNC.m +++ b/screendump/ScreenDumpVNC.m @@ -5,6 +5,7 @@ #import #import #import "IOMobileFramebuffer.h" +#import @implementation ScreenDumpVNC { int _prefsHeight; @@ -38,6 +39,25 @@ +(void)load { ScreenDumpVNC* sharedInstance = [self sharedInstance]; if (![sharedInstance enabled]) return; + + // Tell iOS this is latency-critical work - prevents throttling for daemons + [[NSProcessInfo processInfo] beginActivityWithOptions:(NSActivityLatencyCritical | NSActivityUserInitiated) + reason:@"VNC screen streaming"]; + + // Power assertion to prevent display/GPU throttling + IOPMAssertionID assertionID; + IOPMAssertionCreateWithName(kIOPMAssertionTypePreventUserIdleDisplaySleep, + kIOPMAssertionLevelOn, + CFSTR("VNC screen capture requires display access"), + &assertionID); + + // Also assert we need system activity (prevents CPU throttling) + IOPMAssertionID systemAssertionID; + IOPMAssertionCreateWithName(kIOPMAssertionTypePreventUserIdleSystemSleep, + kIOPMAssertionLevelOn, + CFSTR("VNC server active"), + &systemAssertionID); + [sharedInstance setupScreenInfo]; [sharedInstance startVNCServer]; } diff --git a/screendump/layout/DEBIAN/postinst b/screendump/layout/DEBIAN/postinst index 4cd0202..24c3740 100755 --- a/screendump/layout/DEBIAN/postinst +++ b/screendump/layout/DEBIAN/postinst @@ -1,3 +1,40 @@ #!/bin/sh + +# Setup passwordless SSH for root to localhost (required for screendumpd performance) +SSH_DIR="/var/jb/var/root/.ssh" +KEY_FILE="$SSH_DIR/id_ed25519" +AUTH_KEYS="$SSH_DIR/authorized_keys" + +# Create .ssh directory if it doesn't exist +if [ ! -d "$SSH_DIR" ]; then + mkdir -p "$SSH_DIR" + chmod 700 "$SSH_DIR" +fi + +# Generate ed25519 key if it doesn't exist +if [ ! -f "$KEY_FILE" ]; then + ssh-keygen -t ed25519 -N "" -f "$KEY_FILE" -q + chmod 600 "$KEY_FILE" + chmod 644 "${KEY_FILE}.pub" +fi + +# Add public key to authorized_keys if not already present +if [ -f "${KEY_FILE}.pub" ]; then + PUB_KEY=$(cat "${KEY_FILE}.pub") + + # Create authorized_keys if it doesn't exist + if [ ! -f "$AUTH_KEYS" ]; then + touch "$AUTH_KEYS" + chmod 600 "$AUTH_KEYS" + fi + + # Check if key is already in authorized_keys + if ! grep -qF "$PUB_KEY" "$AUTH_KEYS" 2>/dev/null; then + echo "$PUB_KEY" >> "$AUTH_KEYS" + fi +fi + +# Load the launch daemon launchctl load /var/jb/Library/LaunchDaemons/com.mousen.screendumpd.plist 2> /dev/null -exit 0; \ No newline at end of file + +exit 0 \ No newline at end of file diff --git a/screendump/layout/Library/LaunchDaemons/com.mousen.screendumpd.plist b/screendump/layout/Library/LaunchDaemons/com.mousen.screendumpd.plist index 82941bf..ad6f616 100644 --- a/screendump/layout/Library/LaunchDaemons/com.mousen.screendumpd.plist +++ b/screendump/layout/Library/LaunchDaemons/com.mousen.screendumpd.plist @@ -6,6 +6,14 @@ com.mousen.screendumpd ProgramArguments + /var/jb/usr/bin/ssh + -o + StrictHostKeyChecking=no + -o + UserKnownHostsFile=/dev/null + -o + BatchMode=yes + root@localhost /var/jb/usr/libexec/screendumpd RunAtLoad @@ -22,5 +30,13 @@ Core 9223372036854775807 + ProcessType + Interactive + Nice + -10 + LegacyTimers + + UserName + root diff --git a/screendump/main.m b/screendump/main.m index 1237af1..894908c 100644 --- a/screendump/main.m +++ b/screendump/main.m @@ -1,12 +1,44 @@ #import #import +#import +#import +#import +#import +#import #import "ScreenDumpVNC.h" #import "utils.h" #define kPreferencesNotify "com.mousen.screendump/restart" +static void signalHandler(int sig) { + exit(0); +} + int main(int argc, char *argv[], char *envp[]) { @autoreleasepool { + // Handle termination signals (sent by launchd or when SSH disconnects) + signal(SIGTERM, signalHandler); + signal(SIGHUP, signalHandler); + signal(SIGINT, signalHandler); + + // Monitor parent process - exit if parent (sshd) dies (ppid becomes 1) + pid_t originalPpid = getppid(); + dispatch_source_t parentTimer = dispatch_source_create( + DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)); + dispatch_source_set_timer(parentTimer, DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC, 0); + dispatch_source_set_event_handler(parentTimer, ^{ + if (getppid() != originalPpid) { + // Parent changed (likely died and we got reparented to launchd/init) + exit(0); + } + }); + dispatch_resume(parentTimer); + + // Boost main thread to real-time priority for better daemon performance + struct sched_param param; + param.sched_priority = sched_get_priority_max(SCHED_RR); + pthread_setschedparam(pthread_self(), SCHED_RR, ¶m); + CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), NULL, (CFNotificationCallback)exitProcess, CFSTR(kPreferencesNotify), NULL, CFNotificationSuspensionBehaviorDeliverImmediately); [ScreenDumpVNC load];