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