Skip to content
Snippets Groups Projects
Commit 03c20d84 authored by George Nachman's avatar George Nachman
Browse files

Merge branch 'v2' of https://github.com/gnachman/iTerm2 into v2

parents 0decc17a b89b28ec
No related branches found
No related tags found
No related merge requests found
Loading
Loading
@@ -78,6 +78,7 @@
#import "ColorsMenuItemView.h"
#import "iTermFontPanel.h"
#import "FutureMethods.h"
#import "iTermPromotionalMessageManager.h"
 
#define CACHED_WINDOW_POSITIONS 100
 
Loading
Loading
@@ -4767,6 +4768,13 @@ NSString *sessionsKey = @"sessions";
[TABVIEW processMRUEvent:theEvent];
 
NSUInteger modifierFlags = [theEvent modifierFlags];
if (!(modifierFlags & (NSCommandKeyMask | NSAlternateKeyMask | NSControlKeyMask | NSShiftKeyMask))) {
// Schedule a promotional message when the user has interacted lightly with the app. If a
// message is available to be shown, it'll pop up in a few seconds. This prevents headless
// machines from getting interrupted by a promo.
[[iTermPromotionalMessageManager sharedInstance] scheduleDisplayIfNeeded];
}
if (!(modifierFlags & NSCommandKeyMask) &&
[[[self currentSession] TEXTVIEW] isFindingCursor]) {
// The cmd key was let up while finding the cursor
Loading
Loading
Loading
Loading
@@ -406,6 +406,8 @@
9DB3D6F1176CCABE0071CCF8 /* PrefsArrangements@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 9DB3D6EF176CCABE0071CCF8 /* PrefsArrangements@2x.png */; };
A073973E14C768E400786414 /* ColorsMenuItemView.h in Headers */ = {isa = PBXBuildFile; fileRef = A073973D14C768E400786414 /* ColorsMenuItemView.h */; };
A073974014C7694600786414 /* ColorsMenuItemView.m in Sources */ = {isa = PBXBuildFile; fileRef = A073973F14C7694600786414 /* ColorsMenuItemView.m */; };
A6624F351AFC5EEE0040BA66 /* iTermPromotionalMessageManager.h in Headers */ = {isa = PBXBuildFile; fileRef = A6624F331AFC5EEE0040BA66 /* iTermPromotionalMessageManager.h */; };
A6624F361AFC5EEE0040BA66 /* iTermPromotionalMessageManager.m in Sources */ = {isa = PBXBuildFile; fileRef = A6624F341AFC5EEE0040BA66 /* iTermPromotionalMessageManager.m */; };
A6AA395C18275EB800A19BD5 /* SBSystemPreferences.h in Headers */ = {isa = PBXBuildFile; fileRef = A6AA395B18275EB800A19BD5 /* SBSystemPreferences.h */; };
A6AA395E18275EF200A19BD5 /* ScriptingBridge.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A6AA395D18275EF200A19BD5 /* ScriptingBridge.framework */; };
A6C537B9192DD54A00A08C18 /* OnlineHelp in Resources */ = {isa = PBXBuildFile; fileRef = A6C537B8192DD54A00A08C18 /* OnlineHelp */; };
Loading
Loading
@@ -750,6 +752,8 @@
9DB3D6EF176CCABE0071CCF8 /* PrefsArrangements@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "PrefsArrangements@2x.png"; path = "images/PrefsArrangements@2x.png"; sourceTree = "<group>"; };
A073973D14C768E400786414 /* ColorsMenuItemView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ColorsMenuItemView.h; sourceTree = "<group>"; };
A073973F14C7694600786414 /* ColorsMenuItemView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ColorsMenuItemView.m; sourceTree = "<group>"; };
A6624F331AFC5EEE0040BA66 /* iTermPromotionalMessageManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = iTermPromotionalMessageManager.h; sourceTree = "<group>"; };
A6624F341AFC5EEE0040BA66 /* iTermPromotionalMessageManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = iTermPromotionalMessageManager.m; sourceTree = "<group>"; };
A6AA395B18275EB800A19BD5 /* SBSystemPreferences.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SBSystemPreferences.h; sourceTree = "<group>"; };
A6AA395D18275EF200A19BD5 /* ScriptingBridge.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ScriptingBridge.framework; path = System/Library/Frameworks/ScriptingBridge.framework; sourceTree = SDKROOT; };
A6C537B8192DD54A00A08C18 /* OnlineHelp */ = {isa = PBXFileReference; lastKnownFileType = folder; path = OnlineHelp; sourceTree = "<group>"; };
Loading
Loading
@@ -977,6 +981,8 @@
A073973F14C7694600786414 /* ColorsMenuItemView.m */,
1D70BA331680158700824B72 /* PTYFontInfo.h */,
1D70BA341680158700824B72 /* PTYFontInfo.m */,
A6624F331AFC5EEE0040BA66 /* iTermPromotionalMessageManager.h */,
A6624F341AFC5EEE0040BA66 /* iTermPromotionalMessageManager.m */,
);
name = Classes;
sourceTree = "<group>";
Loading
Loading
@@ -1531,6 +1537,7 @@
1D5FDD5F1208E8F000C46BA3 /* PSMTabBarControl.h in Headers */,
1D5FDD601208E8F000C46BA3 /* PSMTabStyle.h in Headers */,
1D5FDD611208E8F000C46BA3 /* NSBezierPath_AMShading.h in Headers */,
A6624F351AFC5EEE0040BA66 /* iTermPromotionalMessageManager.h in Headers */,
1D5FDD621208E8F000C46BA3 /* PSMTabDragAssistant.h in Headers */,
1D5FDD631208E8F000C46BA3 /* PSMUnifiedTabStyle.h in Headers */,
1D5FDD641208E8F000C46BA3 /* PSMAdiumTabStyle.h in Headers */,
Loading
Loading
@@ -1974,6 +1981,7 @@
1D3D21911482F18A00FAC8E7 /* TmuxController.m in Sources */,
1D3D21961483144600FAC8E7 /* TSVParser.m in Sources */,
1D3D21B014839AAB00FAC8E7 /* TmuxLayoutParser.m in Sources */,
A6624F361AFC5EEE0040BA66 /* iTermPromotionalMessageManager.m in Sources */,
1DB67CEE14850D53005849A1 /* TmuxWindowOpener.m in Sources */,
1DB67CF21485C578005849A1 /* TmuxHistoryParser.m in Sources */,
1DB67CF61486BD3D005849A1 /* TmuxStateParser.m in Sources */,
Loading
Loading
Loading
Loading
@@ -207,3 +207,4 @@
}
 
@end
//
// iTermPromotionalMessageManager.h
// iTerm
//
// Created by George Nachman on 5/7/15.
//
//
#import <Cocoa/Cocoa.h>
@interface iTermPromotionalMessageManager : NSObject
+ (instancetype)sharedInstance;
- (void)scheduleDisplayIfNeeded;
@end
//
// iTermPromotionalMessageManager.m
// iTerm
//
// Created by George Nachman on 5/7/15.
//
//
#import "iTermPromotionalMessageManager.h"
#import "iTermApplicationDelegate.h"
#import "NSStringITerm.h"
static NSString *kPromotionsKey = @"promotions";
static NSString *kPromoIdKey = @"id";
static NSString *kPromoTitleKey = @"title";
static NSString *kPromoMessageKey = @"message";
static NSString *kPromoUrlKey = @"url";
static NSString *kPromoExpirationKey = @"expiration";
static NSString *kPromoMinItermVersionKey = @"min_iterm_version";
static NSString *kPromoMaxItermVersionKey = @"max_iterm_version";
static NSString *const kTimeOfLastPromoKey = @"NoSyncTimeOfLastPromo";
static NSString *const kPromotionsDisabledKey = @"NoSyncDisablePromotions";
static NSString *const kTimeOfLastPromoDownloadKey = @"NoSyncTimeOfLastPromoDownload";
static NSTimeInterval kMinTimeBetweenDownloads = 3600 * 24; // 24 hours
// Gently let the user know about upcoming releases that break backward compatibility.
@interface iTermPromotionalMessageManager ()<NSURLDownloadDelegate>
@property(nonatomic, retain) NSMutableData *data;
@property(nonatomic, copy) NSString *downloadFilename;
@property(nonatomic, retain) NSURLResponse *response;
@end
//#define TEST_PROMOS 1
@implementation iTermPromotionalMessageManager {
NSURLDownload *_download;
NSArray *_promotion; // An array of [ promoId, message, title, url ]
BOOL _scheduled;
NSMutableData *_data;
NSString *_downloadFilename;
NSURLResponse *_response;
}
@synthesize data = _data;
@synthesize downloadFilename = _downloadFilename;
@synthesize response = _response;
+ (instancetype)sharedInstance {
static id instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[self alloc] init];
});
return instance;
}
- (instancetype)init {
DLog(@"Initialize promo manager");
if (!NSClassFromString(@"NSJSONSerialization")) {
DLog(@"JSON serialization not available, abort.");
// This class is needed and is only available on 10.7. We're not going to promo anything
// with an earlier deployment target anyway.
return nil;
}
if ([[NSUserDefaults standardUserDefaults] boolForKey:kPromotionsDisabledKey]) {
DLog(@"Promos disabled");
return nil;
}
if (![[NSUserDefaults standardUserDefaults] boolForKey:@"SUEnableAutomaticChecks"]) {
DLog(@"Auto update disabled");
return nil;
}
self = [super init];
if (self) {
// If there's no value for kTimeOfLastPromoKey set it to "now" to avoid showing a promo
// immediately after the first run of a new install.
if (![[NSUserDefaults standardUserDefaults] objectForKey:kTimeOfLastPromoKey]) {
DLog(@"Initialize last promo time to now because not set");
[[NSUserDefaults standardUserDefaults] setDouble:[NSDate timeIntervalSinceReferenceDate]
forKey:kTimeOfLastPromoKey];
}
[self beginDownload];
}
return self;
}
- (void)dealloc {
[_data release];
[_downloadFilename release];
[_download release];
[_response release];
[super dealloc];
}
- (void)beginDownload {
#if TEST_PROMOS
const NSTimeInterval delay = 1;
#else
// Try again in a day.
const NSTimeInterval delay = kMinTimeBetweenDownloads;
#endif
NSTimeInterval lastDownload =
[[NSUserDefaults standardUserDefaults] doubleForKey:kTimeOfLastPromoDownloadKey];
NSTimeInterval now = [NSDate timeIntervalSinceReferenceDate];
NSTimeInterval elapsedTime = now - lastDownload;
if (elapsedTime < delay) {
DLog(@"Schedule promo download for %f sec from now", (delay - elapsedTime + 1));
// Did a download in the last 24 hours. Schedule another attempt at the right time.
// Add an extra second to avoid any possible edge case.
[self performSelector:@selector(beginDownload)
withObject:nil
afterDelay:delay - elapsedTime + 1];
return;
} else {
// Schedule another download in 24 hours.
DLog(@"Schedule next download for %f seconds from now", delay);
[self performSelector:@selector(beginDownload) withObject:nil afterDelay:delay];
}
DLog(@"Update time of last promo download to %f", now);
[[NSUserDefaults standardUserDefaults] setDouble:now forKey:kTimeOfLastPromoDownloadKey];
if (_download) {
DLog(@"Still downloading (this shouldn't happen)");
// Still downloading (shouldn't happen).
return;
}
NSURLRequest *request = [self request];
if (request) {
DLog(@"Create download.");
_download = [[NSURLDownload alloc] initWithRequest:[self request] delegate:self];
}
}
- (NSURLRequest *)request {
NSString *baseUrl = @"https://iterm2.com/appcasts/promo.json";
NSString *version = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"];
NSString *encodedVersion = [version stringWithPercentEscape];
NSString *urlString = [NSString stringWithFormat:@"%@?v=%@", baseUrl, encodedVersion];
NSURL *url = [NSURL URLWithString:urlString];
if (!url) {
DLog(@"%@ is not a valid url!", urlString);
return nil;
}
NSMutableURLRequest *request =
[NSMutableURLRequest requestWithURL:url
cachePolicy:NSURLRequestReloadIgnoringCacheData
timeoutInterval:30.0];
self.data = [NSMutableData data];
return request;
}
- (NSString *)keyForPromoId:(NSString *)promoId {
NSString *theKey = [NSString stringWithFormat:@"NoSyncHaveShownPromoWithId_%@", promoId];
DLog(@"Key for promo id %@ is %@", promoId, theKey);
return theKey;
}
- (BOOL)haveShownPromoWithId:(NSString *)promoId {
NSString *theKey = [self keyForPromoId:promoId];
BOOL result = [[NSUserDefaults standardUserDefaults] boolForKey:theKey];
DLog(@"have shown promo: %@", @(result));
return result;
}
- (void)setHaveShownPromoWithId:(NSString *)promoId {
DLog(@"Set have shown promo %@", promoId);
NSString *theKey = [self keyForPromoId:promoId];
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:theKey];
[[NSUserDefaults standardUserDefaults] setDouble:[NSDate timeIntervalSinceReferenceDate]
forKey:kTimeOfLastPromoKey];
}
- (NSTimeInterval)timeSinceLastPromo {
NSTimeInterval lastShown =
[[NSUserDefaults standardUserDefaults] doubleForKey:kTimeOfLastPromoKey];
NSTimeInterval result = [NSDate timeIntervalSinceReferenceDate] - lastShown;
DLog(@"Time since last promo is %f", result);
return result;
}
- (void)setPromotionFromDictionary:(NSDictionary *)promo {
DLog(@"Set promo from dict %@", promo);
NSString *promoId = promo[kPromoIdKey];
NSString *message = promo[kPromoMessageKey];
NSString *title = promo[kPromoTitleKey];
NSString *urlString = promo[kPromoUrlKey];
NSURL *url = [NSURL URLWithString:urlString];
[_promotion autorelease];
if (promoId && title && message && url) {
_promotion = [@[ promoId, message, title, url ] retain];
} else {
_promotion = nil;
}
DLog(@"Set promo to %@", _promotion);
}
- (void)showPromotion {
DLog(@"showPromotion");
if (_promotion.count == 4) {
DLog(@"Promo is valid, here we go");
NSString *promoId = _promotion[0];
NSString *title = _promotion[1];
NSString *message = _promotion[2];
NSURL *url = _promotion[3];
[self setHaveShownPromoWithId:promoId];
[_promotion autorelease];
_promotion = nil;
NSAlert *alert = [NSAlert alertWithMessageText:title
defaultButton:@"Learn More"
alternateButton:@"Dismiss"
otherButton:nil
informativeTextWithFormat:@"%@", message];
alert.suppressionButton.title = @"Do not show promotions for new versions of iTerm2 again.";
alert.showsSuppressionButton = YES;
if ([alert runModal] == NSAlertDefaultReturn) {
DLog(@"Open url %@", url);
[[NSWorkspace sharedWorkspace] openURL:url];
}
if (alert.suppressionButton.state == NSOnState) {
DLog(@"Suppress");
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:kPromotionsDisabledKey];
}
}
DLog(@"Set scheduled=NO");
_scheduled = NO;
}
- (BOOL)promotionIsEligible:(NSDictionary *)promo {
NSTimeInterval expiration = [promo[kPromoExpirationKey] doubleValue];
if (expiration) {
if (expiration < [NSDate timeIntervalSinceReferenceDate]) {
DLog(@"Promo ineligible because expiration is %f", expiration);
return NO;
}
}
NSBundle *bundle = [NSBundle mainBundle];
NSString *version = [bundle objectForInfoDictionaryKey:@"CFBundleShortVersionString"];
NSString *minVersion = promo[kPromoMinItermVersionKey];
if (minVersion) {
if ([minVersion compare:version] == NSOrderedDescending) {
DLog(@"Promo ineligible because minVersion is %@ but my version is %@",
minVersion, version);
return NO;
}
}
NSString *maxVersion = promo[kPromoMaxItermVersionKey];
if (maxVersion) {
if ([maxVersion compare:version] == NSOrderedAscending) {
DLog(@"Promo ineligible because maxVersion is %@ but my version is %@",
maxVersion, version);
return NO;
}
}
DLog(@"Promo eligible");
return YES;
}
- (void)loadPromoFromDictionaryIfEligible:(NSDictionary *)root {
NSArray *promotions = root[kPromotionsKey];
for (NSDictionary *promo in promotions) {
#if TEST_PROMOS
const NSTimeInterval minTime = 1;
#else
const NSTimeInterval minTime = 3600 * 24 * 9;
#endif
if (![self haveShownPromoWithId:promo[kPromoIdKey]] &&
[self timeSinceLastPromo] > minTime &&
[self promotionIsEligible:promo]) {
DLog(@"Promo is ok, load it up.");
[self setPromotionFromDictionary:promo];
break;
}
}
}
#pragma mark - NSURLDownloadDelegate
- (void)download:(NSURLDownload *)aDownload decideDestinationWithSuggestedFilename:(NSString *)filename {
DLog(@"Download decide destination with suggested filename %@", filename);
NSString *destinationFilename = NSTemporaryDirectory();
if (destinationFilename) {
destinationFilename = [destinationFilename stringByAppendingPathComponent:filename];
DLog(@"Destination filename is %@", destinationFilename);
[aDownload setDestination:destinationFilename allowOverwrite:NO];
}
}
- (void)download:(NSURLDownload *)aDownload didCreateDestination:(NSString *)path {
DLog(@"did create %@", path);
self.downloadFilename = path;
}
- (void)download:(NSURLDownload *)download didReceiveResponse:(NSURLResponse *)response {
DLog(@"set response to %@", response);
self.response = response;
}
- (void)downloadDidFinish:(NSURLDownload *)aDownload {
DLog(@"Download did finish");
if (self.downloadFilename) {
DLog(@"Have filename %@", self.downloadFilename);
NSData *data = [NSData dataWithContentsOfFile:self.downloadFilename];
[[NSFileManager defaultManager] removeItemAtPath:self.downloadFilename error:nil];
self.downloadFilename = nil;
if (!data) {
DLog(@"No data");
return;
}
if (![_response isKindOfClass:[NSHTTPURLResponse class]]) {
DLog(@"Response class is %@", _response.class);
return;
}
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)_response;
if (httpResponse.statusCode != 200) {
DLog(@"Status code is %@", @(httpResponse.statusCode));
return;
}
NSError *error = nil;
id object = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
if (error && !object) {
DLog(@"JSON deserialization error: %@", error);
return;
} else if ([object isKindOfClass:[NSDictionary class]]) {
DLog(@"Download got a dictionary from json");
NSDictionary *root = object;
[self loadPromoFromDictionaryIfEligible:root];
} else {
DLog(@"Unexpected class for JSON root: %@", [object class]);
}
}
DLog(@"Nil out download");
[_download release];
_download = nil;
}
- (void)download:(NSURLDownload *)aDownload didFailWithError:(NSError *)error {
DLog(@"Download failed: %@", error);
if (self.downloadFilename) {
[[NSFileManager defaultManager] removeItemAtPath:self.downloadFilename error:nil];
}
self.downloadFilename = nil;
[_download release];
_download = nil;
}
- (NSURLRequest *)download:(NSURLDownload *)aDownload
willSendRequest:(NSURLRequest *)request
redirectResponse:(NSURLResponse *)redirectResponse {
DLog(@"Download will send request");
return request;
}
- (void)scheduleDisplayIfNeeded {
DLog(@"Schedule display if needed...");
if (_scheduled || !_promotion) {
DLog(@"Not needed. scheduled=%@", @(_scheduled));
return;
}
DLog(@"Call showPromotion in 5 seconds");
_scheduled = YES;
[self performSelector:@selector(showPromotion) withObject:nil afterDelay:5];
}
@end
2.1
2.1.1
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment