Skip to content

Commit

Permalink
Fixed 251010
Browse files Browse the repository at this point in the history
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
andymatuschak committed Jul 23, 2008
2 parents 675c66f + cec9166 commit c42e3a3
Show file tree
Hide file tree
Showing 29 changed files with 291 additions and 35 deletions.
6 changes: 3 additions & 3 deletions SUAutomaticUpdateAlert.m
Original file line number Diff line number Diff line change
Expand Up @@ -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];
}
Expand Down
6 changes: 3 additions & 3 deletions SUBasicUpdateDriver.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down
5 changes: 3 additions & 2 deletions SUBasicUpdateDriver.m
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions SUHost.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
- (NSString *)displayVersion;
- (NSImage *)icon;
- (BOOL)isRunningOnReadOnlyVolume;
- (BOOL)isBackgroundApplication;
- (NSString *)publicDSAKey;
- (NSArray *)systemProfile;

Expand Down
5 changes: 5 additions & 0 deletions SUHost.m
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
6 changes: 3 additions & 3 deletions SUStatusController.m
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 2 additions & 0 deletions SUUIBasedUpdateDriver.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
SUUpdateAlert *updateAlert;
}

- (void)showModalAlert:(NSAlert *)alert;

@end

#endif
29 changes: 22 additions & 7 deletions SUUIBasedUpdateDriver.m
Original file line number Diff line number Diff line change
Expand Up @@ -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])
Expand All @@ -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];
}

Expand Down Expand Up @@ -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];
}

Expand All @@ -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
6 changes: 3 additions & 3 deletions SUUpdateAlert.m
Original file line number Diff line number Diff line change
Expand Up @@ -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];
}
Expand Down
2 changes: 1 addition & 1 deletion SUUpdateDriver.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
2 changes: 1 addition & 1 deletion SUUpdatePermissionPrompt.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
15 changes: 10 additions & 5 deletions SUUpdatePermissionPrompt.m
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand All @@ -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]];
}

Expand Down
19 changes: 16 additions & 3 deletions SUUserInitiatedUpdateDriver.m
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion SUWindowController.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
4 changes: 2 additions & 2 deletions SUWindowController.m
Original file line number Diff line number Diff line change
Expand Up @@ -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"];
}
Expand Down
2 changes: 1 addition & 1 deletion Sparkle.h
Original file line number Diff line number Diff line change
Expand Up @@ -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__
Expand Down
8 changes: 8 additions & 0 deletions Sparkle.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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>"; };
Expand Down Expand Up @@ -880,6 +884,7 @@
61AAE8710A321F7700D8810D /* nl */,
619B17200E1E9D0800E72754 /* de */,
61F614540E24A12D009F47E7 /* it */,
618915730E35937600B5E981 /* sv */,
);
name = Sparkle.strings;
sourceTree = "<group>";
Expand All @@ -893,6 +898,7 @@
61F365350E1898A3007ECA02 /* nl */,
619B17210E1E9D0800E72754 /* de */,
61F614550E24A12D009F47E7 /* it */,
618915720E35937600B5E981 /* sv */,
);
name = SUAutomaticUpdateAlert.nib;
sourceTree = "<group>";
Expand All @@ -906,6 +912,7 @@
61F365340E1898A3007ECA02 /* nl */,
619B17220E1E9D0800E72754 /* de */,
61F614560E24A12D009F47E7 /* it */,
618915710E35937600B5E981 /* sv */,
);
name = SUUpdateAlert.nib;
sourceTree = "<group>";
Expand Down Expand Up @@ -935,6 +942,7 @@
61F365330E1898A3007ECA02 /* nl */,
619B17230E1E9D0800E72754 /* de */,
61F614570E24A12D009F47E7 /* it */,
618915700E35937600B5E981 /* sv */,
);
name = SUUpdatePermissionPrompt.nib;
sourceTree = "<group>";
Expand Down
50 changes: 50 additions & 0 deletions sv.lproj/SUAutomaticUpdateAlert.nib/classes.nib

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit c42e3a3

Please sign in to comment.