2e3ccbe1a2acf1b69b53e0bc65a915b36eea8851
[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   SUUpdater *updater = [SUUpdater updaterForBundle:
31                                     [NSBundle bundleForClass:[self class]]];
32   [updater setDelegate:self];
33
34   [self awaitScreenSaverTermination:updater];
35 }
36
37
38 // Delegate method that lets us append extra info to the system-info URL.
39 //
40 - (NSArray *) feedParametersForUpdater:(SUUpdater *)updater
41                   sendingSystemProfile:(BOOL)sending
42 {
43   // Get the name of the saver that invoked us, and include that in the
44   // system info.
45   NSString *saver = [[[NSProcessInfo 
46                         processInfo]environment]objectForKey:
47                         @"XSCREENSAVER_CLASSPATH"];
48   if (! saver) return nil;
49   NSString *head = @"org.jwz.xscreensaver.";
50   if ([saver hasPrefix:head])
51     saver = [saver substringFromIndex:[head length]];
52
53   return @[ @{ @"key":          @"saver",
54                @"value":        saver,
55                @"displayKey":   @"Current Saver",
56                @"displayValue": saver
57              }
58           ];
59 }
60
61
62 // Whether ScreenSaverEngine is currently running, meaning screen is blanked.
63 //
64 - (BOOL) screenSaverActive
65 {
66   BOOL found = NO;
67   NSString *target = @"/ScreenSaverEngine.app";
68   ProcessSerialNumber psn = { kNoProcess, kNoProcess };
69   while (GetNextProcess(&psn) == noErr) {
70     CFDictionaryRef cfdict =
71       ProcessInformationCopyDictionary (&psn,
72         kProcessDictionaryIncludeAllInformationMask);
73     if (cfdict) {
74       NSDictionary *dict = (NSDictionary *) cfdict;
75       NSString *path = [dict objectForKey:@"BundlePath"];
76       if (path && [path hasSuffix:target])
77         found = YES;
78       CFRelease (cfdict);
79     }
80     if (found)
81       break;
82   }
83   return found;
84 }
85
86
87 // If the screen saver is not running, then launch the updater.
88 // Otherwise, wait a while and try again.  This is because if the
89 // updater tries to pop up a dialog box while the screen saver is
90 // active, everything goes to hell and it never shows up.
91 //
92 - (void) awaitScreenSaverTermination:(SUUpdater *)updater
93 {
94   if ([self screenSaverActive]) {
95     static float delay = 1;
96     [NSTimer scheduledTimerWithTimeInterval: delay
97              target:self
98              selector:@selector(awaitScreenSaverTerminationTimer:)
99              userInfo:updater
100              repeats:NO];
101     // slightly exponential back-off
102     delay *= 1.3;
103     if (delay > 120)
104       delay = 120;
105   } else {
106     // Launch the updater thread.
107     [updater checkForUpdatesInBackground];
108
109     // Now we need to wait for the Sparkle thread to finish before we can
110     // exit, so just poll waiting for it.
111     //
112     [NSTimer scheduledTimerWithTimeInterval:1
113              target:self
114              selector:@selector(exitWhenDone:)
115              userInfo:updater
116              repeats:YES];
117   }
118 }
119
120
121 - (void) awaitScreenSaverTerminationTimer:(NSTimer *)timer
122 {
123   [self awaitScreenSaverTermination:[timer userInfo]];
124 }
125
126
127 - (void) exitWhenDone:(NSTimer *)timer
128 {
129   SUUpdater *updater = [timer userInfo];
130   if (![updater updateInProgress])
131     [[NSApplication sharedApplication] terminate:self];
132 }
133
134
135 - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)app
136 {
137   return YES;
138 }
139
140 @end