1/* xscreensaver, Copyright (c) 2013-2018 Jamie Zawinski <jwz@jwz.org> 2 * 3 * Permission to use, copy, modify, distribute, and sell this software and its 4 * documentation for any purpose is hereby granted without fee, provided that 5 * the above copyright notice appear in all copies and that both that 6 * copyright notice and this permission notice appear in supporting 7 * documentation. No representations are made about the suitability of this 8 * software for any purpose. It is provided "as is" without express or 9 * implied warranty. 10 * 11 * XScreenSaverUpdater.app -- downloads and installs XScreenSaver updates 12 * via Sparkle.framework. 13 * 14 * Created: 7-Dec-2013 15 * 16 * NOTE: This does not work with Sparkle 1.5b6 -- it requires the "HEAD" 17 * version 4-Dec-2013 or later. 18 */ 19 20#define IN_UPDATER 21#import "Updater.h" 22#import "Sparkle/SUUpdater.h" 23 24@implementation XScreenSaverUpdater : NSObject 25 26- (void)applicationDidFinishLaunching:(NSNotification *)aNotification 27{ 28 NSUserDefaults *defs = [NSUserDefaults standardUserDefaults]; 29 [defs registerDefaults:UPDATER_DEFAULTS]; 30 31 // If it's not time to run the updater, then bail immediately. 32 // I'm not sure why this is necessary, but Sparkle seems to be 33 // checking too often. 34 // 35 if (! [self timeToCheck]) 36 [[NSApplication sharedApplication] terminate:self]; 37 38 // If the screen saver is not running, then launch the updater now. 39 // Otherwise, wait until the screen saver deactivates, and then do 40 // it. This is because if the updater tries to pop up a dialog box 41 // while the screen saver is active, everything goes to hell and it 42 // never shows up. You'd expect the dialog to just map below the 43 // screen saver window, but no. 44 45 if (! [self screenSaverActive]) { 46 [self runUpdater]; 47 } else { 48 // Run the updater when the "screensaver.didstop" notification arrives. 49 [[NSDistributedNotificationCenter defaultCenter] 50 addObserver:self 51 selector:@selector(saverStoppedNotification:) 52 name:@"com.apple.screensaver.didstop" 53 object:nil]; 54 55 // But I'm not sure I trust that, so also poll every couple minutes. 56 timer = [NSTimer scheduledTimerWithTimeInterval: 60 * 2 57 target:self 58 selector:@selector(pollSaverTermination:) 59 userInfo:nil 60 repeats:YES]; 61 } 62} 63 64 65- (BOOL) timeToCheck 66{ 67 NSUserDefaults *defs = [NSUserDefaults standardUserDefaults]; 68 NSTimeInterval interval = [defs doubleForKey:@SUScheduledCheckIntervalKey]; 69 NSDate *last = [defs objectForKey:@SULastCheckTimeKey]; 70 if (!interval || !last) 71 return YES; 72 NSTimeInterval since = [[NSDate date] timeIntervalSinceDate:last]; 73 return (since > interval); 74} 75 76 77// Whether ScreenSaverEngine is currently running, meaning screen is blanked. 78// There's no easy way to determine this other than scanning the process table. 79// 80- (BOOL) screenSaverActive 81{ 82 BOOL found = NO; 83 NSString *target = @"/ScreenSaverEngine.app"; 84 ProcessSerialNumber psn = { kNoProcess, kNoProcess }; 85 while (GetNextProcess(&psn) == noErr) { 86 CFDictionaryRef cfdict = 87 ProcessInformationCopyDictionary (&psn, 88 kProcessDictionaryIncludeAllInformationMask); 89 if (cfdict) { 90 NSDictionary *dict = (NSDictionary *) cfdict; 91 NSString *path = [dict objectForKey:@"BundlePath"]; 92 if (path && [path hasSuffix:target]) 93 found = YES; 94 CFRelease (cfdict); 95 } 96 if (found) 97 break; 98 } 99 return found; 100} 101 102 103- (void) saverStoppedNotification:(NSNotification *)note 104{ 105 [self runUpdater]; 106} 107 108 109- (void) pollSaverTermination:(NSTimer *)t 110{ 111 if (! [self screenSaverActive]) 112 [self runUpdater]; 113} 114 115 116- (void) runUpdater 117{ 118 if (timer) { 119 [timer invalidate]; 120 timer = nil; 121 } 122 123 SUUpdater *updater = [SUUpdater updaterForBundle: 124 [NSBundle bundleForClass:[self class]]]; 125 [updater setDelegate:self]; 126 127 // Launch the updater thread. 128 [updater checkForUpdatesInBackground]; 129 130 // Now we need to wait for the Sparkle thread to finish before we can 131 // exit, so just poll waiting for it. 132 // 133 [NSTimer scheduledTimerWithTimeInterval:1 134 target:self 135 selector:@selector(pollUpdaterTermination:) 136 userInfo:updater 137 repeats:YES]; 138} 139 140 141// Delegate method that lets us append extra info to the system-info URL. 142// 143- (NSArray *) feedParametersForUpdater:(SUUpdater *)updater 144 sendingSystemProfile:(BOOL)sending 145{ 146 // Get the name of the saver that invoked us, and include that in the 147 // system info. 148 NSString *saver = [[[NSProcessInfo processInfo] environment] 149 objectForKey:@"XSCREENSAVER_CLASSPATH"]; 150 if (! saver) return @[]; 151 NSString *head = @"org.jwz.xscreensaver."; 152 if ([saver hasPrefix:head]) 153 saver = [saver substringFromIndex:[head length]]; 154 155 return @[ @{ @"key": @"saver", 156 @"value": saver, 157 @"displayKey": @"Current Saver", 158 @"displayValue": saver 159 } 160 ]; 161} 162 163 164- (void) pollUpdaterTermination:(NSTimer *)t 165{ 166 SUUpdater *updater = [t userInfo]; 167 if (![updater updateInProgress]) 168 [[NSApplication sharedApplication] terminate:self]; 169} 170 171 172- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)app 173{ 174 return YES; 175} 176 177@end 178