Last active
December 21, 2015 01:09
-
-
Save swillits/6225641 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@implementation AppDelegate | |
{ | |
AGApplicationMoved * _moved; | |
} | |
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification | |
{ | |
_moved = [[AGApplicationMoved watchForApplicationMoving:^{ | |
[NSApp terminate:nil]; | |
}] retain]; | |
_moved.allowRelaunch = NO; | |
_moved.allowContinue = YES; | |
} | |
@end | |
// | |
// AGApplicationMoved.h | |
// MovingApplication | |
// | |
// Created by Seth Willits on 8/13/13. | |
// Copyright (c) 2013 Seth Willits. All rights reserved. | |
// | |
#import <Foundation/Foundation.h> | |
@interface AGApplicationMoved : NSObject | |
{ | |
NSURL * _mainBundleFileReferenceURL; | |
void (^_terminationHandler)(void); | |
FSEventStreamRef _stream; | |
BOOL _allowRelaunch; | |
BOOL _allowContinue; | |
NSString * _localizedMessageText; | |
NSString * _localizedInformativeText; | |
} | |
+ (AGApplicationMoved *)watchForApplicationMoving:(void (^)(void))terminationHandler; | |
@property (nonatomic, readwrite) BOOL allowRelaunch; | |
@property (nonatomic, readwrite) BOOL allowContinue; | |
@property (nonatomic, readwrite, copy) NSString * localizedMessageText; | |
@property (nonatomic, readwrite, copy) NSString * localizedInformativeText; | |
@end | |
// | |
// AGApplicationMoved.m | |
// MovingApplication | |
// | |
// Created by Seth Willits on 8/13/13. | |
// Copyright (c) 2013 Seth Willits. All rights reserved. | |
// | |
#import "AGApplicationMoved.h" | |
@interface AGApplicationMoved () | |
- (void)checkForApplicationBeingMoved; | |
- (void)relaunchAfterTermination; | |
@end | |
@implementation AGApplicationMoved | |
+ (AGApplicationMoved *)watchForApplicationMoving:(void (^)(void))terminationHandler; | |
{ | |
return [[[AGApplicationMoved alloc] initWithTerminationHandler:terminationHandler] autorelease]; | |
} | |
- (id)initWithTerminationHandler:(void (^)(void))terminationHandler; | |
{ | |
if (!(self = [super init])) { | |
return nil; | |
} | |
_mainBundleFileReferenceURL = [[[[NSBundle mainBundle] bundleURL] fileReferenceURL] retain]; | |
_terminationHandler = [terminationHandler copy]; | |
FSEventStreamContext context; | |
context.copyDescription = NULL; | |
context.retain = NULL; | |
context.release = NULL; | |
context.info = self; | |
context.version = 0; | |
_stream = FSEventStreamCreate(NULL, MyFSEventStreamCallback, &context, (CFArrayRef)@[_mainBundleFileReferenceURL.relativePath], kFSEventStreamEventIdSinceNow, 0.0, kFSEventStreamCreateFlagUseCFTypes | kFSEventStreamCreateFlagWatchRoot); | |
if (_stream) { | |
FSEventStreamScheduleWithRunLoop(_stream, CFRunLoopGetMain(), kCFRunLoopDefaultMode); | |
FSEventStreamStart(_stream); | |
} else { | |
[self release]; | |
return nil; | |
} | |
_allowRelaunch = NO; | |
_allowContinue = YES; | |
return self; | |
} | |
- (void)dealloc; | |
{ | |
if (_stream) { | |
FSEventStreamStop(_stream); | |
FSEventStreamInvalidate(_stream); | |
FSEventStreamRelease(_stream); | |
} | |
[_terminationHandler release]; | |
[_mainBundleFileReferenceURL release]; | |
[_localizedMessageText release]; | |
[_localizedInformativeText release]; | |
[super dealloc]; | |
} | |
@synthesize allowRelaunch = _allowRelaunch; | |
@synthesize allowContinue = _allowContinue; | |
@synthesize localizedMessageText = _localizedMessageText; | |
@synthesize localizedInformativeText = _localizedInformativeText; | |
- (NSString *)localizedMessageText; | |
{ | |
if (_localizedMessageText) { | |
return _localizedMessageText; | |
} | |
NSString * appName = NSRunningApplication.currentApplication.localizedName; | |
return [NSString stringWithFormat:@"%@ was moved or renamed while running which can cause problems.", appName]; | |
} | |
- (NSString *)localizedInformativeText; | |
{ | |
if (_localizedInformativeText) { | |
return _localizedInformativeText; | |
} | |
NSString * appName = NSRunningApplication.currentApplication.localizedName; | |
NSString * inform = nil; | |
if (self.allowContinue) { | |
inform = [NSString stringWithFormat:@"Please quit and relaunch %@, or move the application back to its original location before continuing.", appName]; | |
} else { | |
inform = [NSString stringWithFormat:@"Please quit and relaunch %@.", appName]; | |
} | |
return inform; | |
} | |
- (void)checkForApplicationBeingMoved; | |
{ | |
if (![_mainBundleFileReferenceURL.relativePath isEqual:NSBundle.mainBundle.bundlePath]) { | |
NSString * defaultBtn = (self.allowRelaunch ? NSLocalizedString(@"Relaunch", nil) : NSLocalizedString(@"Quit", nil)); | |
NSString * continueBtn = (self.allowContinue ? NSLocalizedString(@"Continue", nil) : nil); | |
NSAlert * alert = [NSAlert alertWithMessageText:self.localizedMessageText defaultButton:defaultBtn alternateButton:continueBtn otherButton:nil informativeTextWithFormat:@"%@", self.localizedInformativeText]; | |
NSUInteger response = [alert runModal]; | |
if (response == NSAlertDefaultReturn) { | |
if (self.allowRelaunch) { | |
[self relaunchAfterTermination]; | |
} | |
if (_terminationHandler) { | |
_terminationHandler(); | |
} else { | |
[NSApp terminate:nil]; | |
} | |
} | |
} | |
} | |
static void MyFSEventStreamCallback(ConstFSEventStreamRef streamRef, void *clientCallBackInfo, size_t numEvents, void *eventPaths, const FSEventStreamEventFlags eventFlags[], const FSEventStreamEventId eventIds[]) | |
{ | |
for (size_t i = 0; i < numEvents; i++) { | |
BOOL rootChanged = ((eventFlags[i] & kFSEventStreamEventFlagRootChanged) > 0); | |
if (rootChanged) { | |
[(AGApplicationMoved *)clientCallBackInfo checkForApplicationBeingMoved]; | |
} | |
} | |
} | |
- (void)relaunchAfterTermination; | |
{ | |
// The shell script waits until the original app process terminates. | |
// This is done so that the relaunched app opens as the front-most app. | |
NSString * script = [NSString stringWithFormat:@"(while [ `ps -p %d | wc -l` -gt 1 ]; do sleep 0.1; done; open '%@') &", | |
NSProcessInfo.processInfo.processIdentifier, | |
_mainBundleFileReferenceURL.relativePath]; | |
@try { | |
[NSTask launchedTaskWithLaunchPath:@"/bin/sh" arguments:@[@"-c", script]]; | |
} | |
@catch (id e) {} | |
} | |
@end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment