Permalink
Browse files

Fixed 251010

Fixes from Matt Stevens:
"For background applications (menu bar, completely UI-less, etc) there are a couple of issues with Sparkle notifications:

- When prompts such as the initial prompt to enable update checking are displayed they can be hidden behind other windows since the background app is not in focus. This can cause problems, as these prompts run modally and can stop the application from functioning without the user knowing why.

- If the update notification window is displayed and the user clicks away to another application, the window disappears and there is no way to get it back since there is no other UI to cause the app to activate. In this case the update should probably operate as a standard window since it is effectively operating as the application's UI."
  • Loading branch information...
2 parents 675c66f + cec9166 commit c42e3a3f476566bec351d2b02435700b0c082f0d @andymatuschak andymatuschak committed Jul 23, 2008
@@ -11,14 +11,14 @@
@implementation SUAutomaticUpdateAlert
-- (id)initWithAppcastItem:(SUAppcastItem *)item host:(SUHost *)hb delegate:del;
+- (id)initWithAppcastItem:(SUAppcastItem *)item host:(SUHost *)aHost delegate:del;
{
- self = [super initWithHost:hb windowNibName:@"SUAutomaticUpdateAlert"];
+ self = [super initWithHost:aHost windowNibName:@"SUAutomaticUpdateAlert"];
if (self)
{
updateItem = [item retain];
delegate = del;
- host = [hb retain];
+ host = [aHost retain];
[self setShouldCascadeWindows:NO];
[[self window] center];
}
@@ -22,7 +22,7 @@
NSString *relaunchPath;
}
-- (void)checkForUpdatesAtURL:(NSURL *)appcastURL host:(SUHost *)hb;
+- (void)checkForUpdatesAtURL:(NSURL *)appcastURL host:(SUHost *)host;
- (void)appcastDidFinishLoading:(SUAppcast *)ac;
- (void)appcast:(SUAppcast *)ac failedToLoadWithError:(NSError *)error;
@@ -44,8 +44,8 @@
- (void)unarchiverDidFail:(SUUnarchiver *)ua;
- (void)installUpdate;
-- (void)installerFinishedForHost:(SUHost *)hb;
-- (void)installerForHost:(SUHost *)hb failedWithError:(NSError *)error;
+- (void)installerFinishedForHost:(SUHost *)host;
+- (void)installerForHost:(SUHost *)host failedWithError:(NSError *)error;
- (void)relaunchHostApp;
- (void)cleanUp;
@@ -166,8 +166,9 @@ - (void)download:(NSURLDownload *)download didFailWithError:(NSError *)error
- (BOOL)download:(NSURLDownload *)download shouldDecodeSourceDataOfMIMEType:(NSString *)encodingType
{
- if ([encodingType isEqualToString:@"application/gzip"]) return NO;
- return YES;
+ // We don't want the download system to extract our gzips.
+ // Note that we use a substring matching here instead of direct comparison because the docs say "application/gzip" but the system *uses* "application/x-gzip". This is a documentation bug.
+ return ([encodingType rangeOfString:@"gzip"].location == NSNotFound);
}
- (void)extractUpdate
View
@@ -20,6 +20,7 @@
- (NSString *)displayVersion;
- (NSImage *)icon;
- (BOOL)isRunningOnReadOnlyVolume;
+- (BOOL)isBackgroundApplication;
- (NSString *)publicDSAKey;
- (NSArray *)systemProfile;
View
@@ -85,6 +85,11 @@ - (BOOL)isRunningOnReadOnlyVolume
return (statfs_info.f_flags & MNT_RDONLY);
}
+- (BOOL)isBackgroundApplication
+{
+ return [[bundle objectForInfoDictionaryKey:@"LSUIElement"] doubleValue];
+}
+
- (NSString *)publicDSAKey
{
// Maybe the key is just a string in the Info.plist.
@@ -11,12 +11,12 @@
@implementation SUStatusController
-- (id)initWithHost:(SUHost *)hb
+- (id)initWithHost:(SUHost *)aHost
{
- self = [super initWithHost:hb windowNibName:@"SUStatus"];
+ self = [super initWithHost:aHost windowNibName:@"SUStatus"];
if (self)
{
- host = [hb retain];
+ host = [aHost retain];
[self setShouldCascadeWindows:NO];
}
return self;
@@ -18,6 +18,8 @@
SUUpdateAlert *updateAlert;
}
+- (void)showModalAlert:(NSAlert *)alert;
+
@end
#endif
@@ -16,12 +16,19 @@ - (void)didFindValidUpdate
updateAlert = [[SUUpdateAlert alloc] initWithAppcastItem:updateItem host:host];
[updateAlert setDelegate:self];
- // If the app is a menubar app or the like, we need to focus it first:
- if ([[host objectForInfoDictionaryKey:@"LSUIElement"] doubleValue]) { [NSApp activateIgnoringOtherApps:YES]; }
-
SUUpdater *updater = [SUUpdater updaterForBundle:[host bundle]];
if ([[updater delegate] respondsToSelector:@selector(updater:didFindValidUpdate:)])
[[updater delegate] updater:updater didFindValidUpdate:updateItem];
+
+ // If the app is a menubar app or the like, we need to focus it first and alter the
+ // update prompt to behave like a normal window. Otherwise if the window were hidden
+ // there may be no way for the application to be activated to make it visible again.
+ if ([host isBackgroundApplication])
+ {
+ [[updateAlert window] setHidesOnDeactivate:NO];
+ [[updateAlert window] setLevel:NSNormalWindowLevel];
+ [NSApp activateIgnoringOtherApps:YES];
+ }
// Only show the update alert if the app is active; otherwise, we'll wait until it is.
if ([NSApp isActive])
@@ -36,8 +43,7 @@ - (void)didNotFindUpdate
if ([[updater delegate] respondsToSelector:@selector(updaterDidNotFindUpdate:)])
[[updater delegate] updaterDidNotFindUpdate:updater];
NSAlert *alert = [NSAlert alertWithMessageText:SULocalizedString(@"You're up to date!", nil) defaultButton:SULocalizedString(@"OK", nil) alternateButton:nil otherButton:nil informativeTextWithFormat:SULocalizedString(@"%@ %@ is currently the newest version available.", nil), [host name], [host displayVersion]];
- [alert setIcon:[host icon]];
- [alert runModal];
+ [self showModalAlert:alert];
[self abortUpdate];
}
@@ -141,8 +147,7 @@ - (void)installUpdate
- (void)abortUpdateWithError:(NSError *)error
{
NSAlert *alert = [NSAlert alertWithMessageText:SULocalizedString(@"Update Error!", nil) defaultButton:SULocalizedString(@"Cancel Update", nil) alternateButton:nil otherButton:nil informativeTextWithFormat:[error localizedDescription]];
- [alert setIcon:[host icon]];
- [alert runModal];
+ [self showModalAlert:alert];
[super abortUpdateWithError:error];
}
@@ -156,4 +161,14 @@ - (void)abortUpdate
[super abortUpdate];
}
+- (void)showModalAlert:(NSAlert *)alert
+{
+ // When showing a modal alert we need to ensure that background applications
+ // are focused to inform the user since there is no dock icon to notify them.
+ if ([host isBackgroundApplication]) { [NSApp activateIgnoringOtherApps:YES]; }
+
+ [alert setIcon:[host icon]];
+ [alert runModal];
+}
+
@end
View
@@ -13,12 +13,12 @@
@implementation SUUpdateAlert
-- (id)initWithAppcastItem:(SUAppcastItem *)item host:(SUHost *)hb
+- (id)initWithAppcastItem:(SUAppcastItem *)item host:(SUHost *)aHost
{
- self = [super initWithHost:hb windowNibName:@"SUUpdateAlert"];
+ self = [super initWithHost:host windowNibName:@"SUUpdateAlert"];
if (self)
{
- host = [hb retain];
+ host = [aHost retain];
updateItem = [item retain];
[self setShouldCascadeWindows:NO];
}
View
@@ -20,7 +20,7 @@ extern NSString *SUUpdateDriverFinishedNotification;
BOOL finished;
}
-- (void)checkForUpdatesAtURL:(NSURL *)appcastURL host:(SUHost *)hb;
+- (void)checkForUpdatesAtURL:(NSURL *)appcastURL host:(SUHost *)host;
- (void)abortUpdate;
- (BOOL)finished;
@@ -25,7 +25,7 @@ typedef enum {
IBOutlet NSButton *moreInfoButton;
BOOL isShowingMoreInfo, shouldSendProfile;
}
-+ (void)promptWithHost:(SUHost *)hb delegate:(id)d;
++ (void)promptWithHost:(SUHost *)aHost delegate:(id)d;
- (IBAction)toggleMoreInfo:(id)sender;
- (IBAction)finishPrompt:(id)sender;
@end
@@ -16,12 +16,12 @@ - (BOOL)shouldAskAboutProfile
return [[host objectForInfoDictionaryKey:SUEnableSystemProfilingKey] boolValue];
}
-- (id)initWithHost:(SUHost *)hb delegate:(id)d
+- (id)initWithHost:(SUHost *)aHost delegate:(id)d
{
- self = [super initWithHost:hb windowNibName:@"SUUpdatePermissionPrompt"];
+ self = [super initWithHost:aHost windowNibName:@"SUUpdatePermissionPrompt"];
if (self)
{
- host = [hb retain];
+ host = [aHost retain];
delegate = [d retain];
isShowingMoreInfo = NO;
shouldSendProfile = [self shouldAskAboutProfile];
@@ -30,9 +30,14 @@ - (id)initWithHost:(SUHost *)hb delegate:(id)d
return self;
}
-+ (void)promptWithHost:(SUHost *)hb delegate:(id)d
++ (void)promptWithHost:(SUHost *)host delegate:(id)d
{
- id prompt = [[[self class] alloc] initWithHost:hb delegate:d];
+ // If this is a background application we need to focus it in order to bring the prompt
+ // to the user's attention. Otherwise the prompt would be hidden behind other applications and
+ // the user would not know why the application was paused.
+ if ([host isBackgroundApplication]) { [NSApp activateIgnoringOtherApps:YES]; }
+
+ id prompt = [[[self class] alloc] initWithHost:host delegate:d];
[NSApp runModalForWindow:[prompt window]];
}
@@ -11,14 +11,21 @@
@implementation SUUserInitiatedUpdateDriver
-- (void)checkForUpdatesAtURL:(NSURL *)appcastURL host:(SUHost *)hb
+- (void)checkForUpdatesAtURL:(NSURL *)appcastURL host:(SUHost *)aHost
{
- checkingController = [[SUStatusController alloc] initWithHost:hb];
+ checkingController = [[SUStatusController alloc] initWithHost:aHost];
[[checkingController window] center]; // Force the checking controller to load its window.
[checkingController beginActionWithTitle:SULocalizedString(@"Checking for updates\u2026", nil) maxProgressValue:0 statusText:nil];
[checkingController setButtonTitle:SULocalizedString(@"Cancel", nil) target:self action:@selector(cancelCheckForUpdates:) isDefault:NO];
[checkingController showWindow:self];
- [super checkForUpdatesAtURL:appcastURL host:hb];
+ [super checkForUpdatesAtURL:appcastURL host:host];
+
+ // For background applications, obtain focus.
+ // Useful if the update check is requested from another app like System Preferences.
+ if ([host isBackgroundApplication])
+ {
+ [NSApp activateIgnoringOtherApps:YES];
+ }
}
- (void)closeCheckingWindow
@@ -54,6 +61,12 @@ - (void)abortUpdateWithError:(NSError *)error
[super abortUpdateWithError:error];
}
+- (void)abortUpdate
+{
+ [self closeCheckingWindow];
+ [super abortUpdate];
+}
+
- (void)appcast:(SUAppcast *)ac failedToLoadWithError:(NSError *)error
{
if (isCanceled)
@@ -14,7 +14,7 @@
@class SUHost;
@interface SUWindowController : NSWindowController { }
// We use this instead of plain old NSWindowController initWithWindowNibName so that we'll be able to find the right path when running in a bundle loaded from another app.
-- (id)initWithHost:(SUHost *)hb windowNibName:(NSString *)nibName;
+- (id)initWithHost:(SUHost *)host windowNibName:(NSString *)nibName;
@end
#endif
@@ -10,12 +10,12 @@
@implementation SUWindowController
-- (id)initWithHost:(SUHost *)hb windowNibName:(NSString *)nibName
+- (id)initWithHost:(SUHost *)host windowNibName:(NSString *)nibName
{
NSString *path = [[NSBundle bundleForClass:[self class]] pathForResource:nibName ofType:@"nib"];
if (path == nil) // Slight hack to resolve issues with running Sparkle in debug configurations.
{
- NSString *frameworkPath = [[hb sharedFrameworksPath] stringByAppendingPathComponent:@"Sparkle.framework"];
+ NSString *frameworkPath = [[host sharedFrameworksPath] stringByAppendingPathComponent:@"Sparkle.framework"];
NSBundle *framework = [NSBundle bundleWithPath:frameworkPath];
path = [framework pathForResource:nibName ofType:@"nib"];
}
View
@@ -13,7 +13,7 @@
#ifndef SPARKLE_H
#define SPARKLE_H
-#define SULocalizedString(key,comment) NSLocalizedStringFromTableInBundle(key, @"Sparkle", [NSBundle bundleForClass:[self class]], comment)
+#define SULocalizedString(key,comment) NSLocalizedStringFromTableInBundle(key, @"Sparkle", [NSBundle bundleWithIdentifier:@"org.andymatuschak.Sparkle"], comment)
#define SUAbstractFail() NSAssert2(nil, @"Can't call %@ on an instance of %@; this is an abstract method!", __PRETTY_FUNCTION__, [self class]);
#ifdef __OBJC__
@@ -189,6 +189,10 @@
615AE3CF0D64DC40001CA7BD /* SUModelTranslation.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = SUModelTranslation.plist; sourceTree = "<group>"; };
6171D9050D57B81800BFE886 /* NSFileManager+Aliases.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSFileManager+Aliases.h"; sourceTree = "<group>"; };
6171D9060D57B81800BFE886 /* NSFileManager+Aliases.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSFileManager+Aliases.m"; sourceTree = "<group>"; };
+ 618915700E35937600B5E981 /* sv */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = sv; path = sv.lproj/SUUpdatePermissionPrompt.nib; sourceTree = "<group>"; };
+ 618915710E35937600B5E981 /* sv */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = sv; path = sv.lproj/SUUpdateAlert.nib; sourceTree = "<group>"; };
+ 618915720E35937600B5E981 /* sv */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = sv; path = sv.lproj/SUAutomaticUpdateAlert.nib; sourceTree = "<group>"; };
+ 618915730E35937600B5E981 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Sparkle.strings; sourceTree = "<group>"; };
618FA4FF0DAE88B40026945C /* SUInstaller.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SUInstaller.h; sourceTree = "<group>"; };
618FA5000DAE88B40026945C /* SUInstaller.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SUInstaller.m; sourceTree = "<group>"; };
618FA5030DAE8AB80026945C /* SUPlainInstaller.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SUPlainInstaller.h; sourceTree = "<group>"; };
@@ -880,6 +884,7 @@
61AAE8710A321F7700D8810D /* nl */,
619B17200E1E9D0800E72754 /* de */,
61F614540E24A12D009F47E7 /* it */,
+ 618915730E35937600B5E981 /* sv */,
);
name = Sparkle.strings;
sourceTree = "<group>";
@@ -893,6 +898,7 @@
61F365350E1898A3007ECA02 /* nl */,
619B17210E1E9D0800E72754 /* de */,
61F614550E24A12D009F47E7 /* it */,
+ 618915720E35937600B5E981 /* sv */,
);
name = SUAutomaticUpdateAlert.nib;
sourceTree = "<group>";
@@ -906,6 +912,7 @@
61F365340E1898A3007ECA02 /* nl */,
619B17220E1E9D0800E72754 /* de */,
61F614560E24A12D009F47E7 /* it */,
+ 618915710E35937600B5E981 /* sv */,
);
name = SUUpdateAlert.nib;
sourceTree = "<group>";
@@ -935,6 +942,7 @@
61F365330E1898A3007ECA02 /* nl */,
619B17230E1E9D0800E72754 /* de */,
61F614570E24A12D009F47E7 /* it */,
+ 618915700E35937600B5E981 /* sv */,
);
name = SUUpdatePermissionPrompt.nib;
sourceTree = "<group>";
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>IBClasses</key>
+ <array>
+ <dict>
+ <key>CLASS</key>
+ <string>SUWindowController</string>
+ <key>LANGUAGE</key>
+ <string>ObjC</string>
+ <key>SUPERCLASS</key>
+ <string>NSWindowController</string>
+ </dict>
+ <dict>
+ <key>ACTIONS</key>
+ <dict>
+ <key>doNotInstall</key>
+ <string>id</string>
+ <key>installLater</key>
+ <string>id</string>
+ <key>installNow</key>
+ <string>id</string>
+ </dict>
+ <key>CLASS</key>
+ <string>SUAutomaticUpdateAlert</string>
+ <key>LANGUAGE</key>
+ <string>ObjC</string>
+ <key>SUPERCLASS</key>
+ <string>SUWindowController</string>
+ </dict>
+ <dict>
+ <key>CLASS</key>
+ <string>FirstResponder</string>
+ <key>LANGUAGE</key>
+ <string>ObjC</string>
+ <key>SUPERCLASS</key>
+ <string>NSObject</string>
+ </dict>
+ <dict>
+ <key>CLASS</key>
+ <string>NSObject</string>
+ <key>LANGUAGE</key>
+ <string>ObjC</string>
+ </dict>
+ </array>
+ <key>IBVersion</key>
+ <string>1</string>
+</dict>
+</plist>
Oops, something went wrong. Retry.

0 comments on commit c42e3a3

Please sign in to comment.