/* HBAppDelegate.m $
This file is part of the HandBrake source code.
Homepage: .
It may be used under the terms of the GNU General Public License. */
#import "HBAppDelegate.h"
#import "HBQueue.h"
#import "HBUtilities.h"
#import "HBPresetsManager.h"
#import "HBPreset.h"
#import "HBPresetsMenuBuilder.h"
#import "HBPreferencesController.h"
#import "HBQueueController.h"
#import "HBOutputPanelController.h"
#import "HBController.h"
@import HandBrakeKit;
#define PRESET_FILE @"UserPresets.json"
#define QUEUE_FILE @"Queue.hbqueue"
@interface HBAppDelegate ()
@property (nonatomic, strong) HBPresetsManager *presetsManager;
@property (nonatomic, strong) HBPresetsMenuBuilder *presetsMenuBuilder;
@property (nonatomic, unsafe_unretained) IBOutlet NSMenu *presetsMenu;
@property (nonatomic, strong) HBPreferencesController *preferencesController;
@property (nonatomic, strong) HBQueue *queue;
@property (nonatomic, strong) HBQueueController *queueController;
@property (nonatomic, strong) HBOutputPanelController *outputPanel;
@property (nonatomic, strong) HBController *mainController;
@end
@implementation HBAppDelegate
- (instancetype)init
{
self = [super init];
if (self)
{
// Register the default preferences
[HBPreferencesController registerUserDefaults];
[HBCore initGlobal];
[HBCore registerErrorHandler:^(NSString *error) {
fprintf(stderr, "error: %s\n", error.UTF8String);
}];
[HBCore setDVDNav:[NSUserDefaults.standardUserDefaults boolForKey:HBUseDvdNav]];
_outputPanel = [[HBOutputPanelController alloc] init];
// we init the HBPresetsManager
NSURL *appSupportURL = HBUtilities.appSupportURL;
_presetsManager = [[HBPresetsManager alloc] initWithURL:[appSupportURL URLByAppendingPathComponent:PRESET_FILE]];
// Queue
_queue = [[HBQueue alloc] initWithURL:[appSupportURL URLByAppendingPathComponent:QUEUE_FILE]];
_queueController = [[HBQueueController alloc] initWithQueue:_queue];
_queueController.delegate = self;
_mainController = [[HBController alloc] initWithDelegate:self queue:_queue presetsManager:_presetsManager];
}
return self;
}
#pragma mark - NSApplicationDelegate
- (BOOL)applicationShouldHandleReopen:(NSApplication *)theApplication hasVisibleWindows:(BOOL)flag
{
if (!flag)
{
[self.mainController showWindow:nil];
}
return YES;
}
- (void)applicationDidFinishLaunching:(NSNotification *)notification
{
NSUserDefaults *ud = NSUserDefaults.standardUserDefaults;
// Reset "When done" action
if ([ud boolForKey:HBResetWhenDoneOnLaunch])
{
[ud setInteger:HBDoneActionDoNothing forKey:HBAlertWhenDone];
}
if (@available (macOS 10.12.2, *))
{
NSApplication.sharedApplication.automaticCustomizeTouchBarMenuItemEnabled = YES;
}
self.presetsMenuBuilder = [[HBPresetsMenuBuilder alloc] initWithMenu:self.presetsMenu
action:@selector(selectPresetFromMenu:)
size:NSFont.systemFontSize
presetsManager:self.presetsManager];
[self.presetsMenuBuilder build];
// Get the number of HandBrake instances currently running
NSUInteger instances = [NSRunningApplication runningApplicationsWithBundleIdentifier:NSBundle.mainBundle.bundleIdentifier].count;
// Open debug output window now if it was visible when HB was closed
if ([ud boolForKey:@"OutputPanelIsOpen"])
{
[self showOutputPanel:nil];
}
// On Screen Notification
// We check to see if there is already another instance of hb running.
if (instances > 1)
{
NSAlert *alert = [[NSAlert alloc] init];
[alert setMessageText:NSLocalizedString(@"There is already an instance of HandBrake running.", @"Queue -> Multiple instances alert message")];
[alert setInformativeText:NSLocalizedString(@"The queue will be shared between the instances.", @"Queue -> Multiple instances alert informative text")];
[alert runModal];
}
else
{
[self.queue setEncodingJobsAsPending];
[self.queue removeCompletedAndCancelledItems];
}
// Now we re-check the queue array to see if there are
// any remaining encodes to be done
if (self.queue.items.count)
{
[self showMainWindow:self];
[self showQueueWindow:self];
}
else
{
// Open queue window now if it was visible when HB was closed
if ([ud boolForKey:@"QueueWindowIsOpen"])
{
[self showQueueWindow:nil];
}
[self showMainWindow:self];
[self.mainController launchAction];
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
// Remove encodes logs older than a month
if ([ud boolForKey:HBClearOldLogs])
{
[self cleanEncodeLogs];
}
// If we are a single instance it is safe to clean up the previews if there are any
// left over. This is a bit of a kludge but will prevent a build up of old instance
// live preview cruft. No danger of removing an active preview directory since they
// are created later in HBPreviewController if they don't exist at the moment a live
// preview encode is initiated.
if (instances == 1)
{
[self cleanPreviews];
}
});
}
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)app
{
if (self.queue.isEncoding)
{
NSAlert *alert = [[NSAlert alloc] init];
[alert setMessageText:NSLocalizedString(@"Are you sure you want to quit HandBrake?", @"Quit Alert -> message")];
[alert setInformativeText:NSLocalizedString(@"If you quit HandBrake your current encode will be reloaded into your queue at next launch. Do you want to quit anyway?", @"Quit Alert -> informative text")];
[alert addButtonWithTitle:NSLocalizedString(@"Quit", @"Quit Alert -> first button")];
[alert addButtonWithTitle:NSLocalizedString(@"Don't Quit", @"Quit Alert -> second button")];
[alert.buttons[1] setKeyEquivalent:@"\E"];
[alert setAlertStyle:NSAlertStyleCritical];
NSInteger result = [alert runModal];
if (result == NSAlertFirstButtonReturn)
{
return NSTerminateNow;
}
else
{
return NSTerminateCancel;
}
}
return NSTerminateNow;
}
- (void)applicationWillTerminate:(NSNotification *)notification
{
[self.presetsManager savePresets];
[NSUserDefaults.standardUserDefaults setBool:_queueController.window.isVisible forKey:@"QueueWindowIsOpen"];
[NSUserDefaults.standardUserDefaults setBool:_outputPanel.window.isVisible forKey:@"OutputPanelIsOpen"];
_mainController = nil;
_queueController = nil;
_queue = nil;
[HBCore closeGlobal];
}
- (void)application:(NSApplication *)sender openFiles:(NSArray *)filenames
{
[self.mainController openURL:[NSURL fileURLWithPath:filenames.firstObject]];
[NSApp replyToOpenOrPrint:NSApplicationDelegateReplySuccess];
}
- (BOOL)validateMenuItem:(NSMenuItem *)menuItem
{
SEL action = menuItem.action;
if (action == @selector(toggleStartCancel:) || action == @selector(togglePauseResume:))
{
// Delegate the validation to the queue controller
return [self.queueController validateMenuItem:menuItem];
}
else if (action == @selector(showAddPresetPanel:) ||
action == @selector(showPreviewWindow:) ||
action == @selector(browseSources:))
{
// Delegate the validation to the main controller
return [self.mainController validateMenuItem:menuItem];
}
return YES;
}
#pragma mark - Clean ups
/**
* Clears the EncodeLogs folder, removes the logs
* older than a month.
*/
- (void)cleanEncodeLogs
{
NSURL *directoryUrl = [HBUtilities.appSupportURL URLByAppendingPathComponent:@"EncodeLogs"];
if (directoryUrl)
{
NSArray *contents = [[NSFileManager defaultManager] contentsOfDirectoryAtURL:directoryUrl
includingPropertiesForKeys:nil
options:NSDirectoryEnumerationSkipsSubdirectoryDescendants |
NSDirectoryEnumerationSkipsHiddenFiles |
NSDirectoryEnumerationSkipsPackageDescendants
error:NULL];
NSDate *limit = [NSDate dateWithTimeIntervalSinceNow: -(60 * 60 * 24 * 30)];
NSFileManager *manager = [[NSFileManager alloc] init];
for (NSURL *fileURL in contents)
{
NSDate *creationDate = nil;
[fileURL getResourceValue:&creationDate forKey:NSURLCreationDateKey error:NULL];
if ([creationDate isLessThan:limit])
{
[manager removeItemAtURL:fileURL error:NULL];
}
}
}
}
- (void)cleanPreviews
{
NSURL *previewDirectory = [HBUtilities.appSupportURL URLByAppendingPathComponent:@"Previews"];
if (previewDirectory)
{
NSArray *contents = [NSFileManager.defaultManager contentsOfDirectoryAtURL:previewDirectory
includingPropertiesForKeys:nil
options:NSDirectoryEnumerationSkipsSubdirectoryDescendants |
NSDirectoryEnumerationSkipsPackageDescendants
error:NULL];
NSFileManager *manager = [[NSFileManager alloc] init];
for (NSURL *url in contents)
{
NSError *error = nil;
BOOL result = [manager removeItemAtURL:url error:&error];
if (result == NO && error)
{
[HBUtilities writeToActivityLog: "Could not remove existing preview at : %s", url.lastPathComponent.UTF8String];
}
}
}
}
#pragma mark - Rescan job
- (void)openJob:(HBJob *)job completionHandler:(void (^)(BOOL result))handler
{
[self.mainController openJob:job completionHandler:handler];
}
#pragma mark - Menu actions
- (IBAction)toggleStartCancel:(id)sender
{
[self.queueController toggleStartCancel:sender];
}
- (IBAction)togglePauseResume:(id)sender
{
[self.queueController togglePauseResume:sender];
}
- (IBAction)browseSources:(id)sender
{
[self.mainController browseSources:sender];
}
#pragma mark - Presets Menu actions
/**
* We use this method to recreate new, updated factory presets
*/
- (IBAction)addFactoryPresets:(id)sender
{
[self.presetsManager generateBuiltInPresets];
}
- (IBAction)reloadPreset:(id)sender
{
[self.mainController reloadPreset:sender];
}
#pragma mark - Show Window Menu Items
/**
* Shows preferences window.
*/
- (IBAction)showPreferencesWindow:(id)sender
{
if (_preferencesController == nil)
{
_preferencesController = [[HBPreferencesController alloc] init];
}
[self.preferencesController showWindow:sender];
}
/**
* Shows queue window.
*/
- (IBAction)showQueueWindow:(id)sender
{
[self.queueController showWindow:sender];
}
/**
* Shows debug output window.
*/
- (IBAction)showOutputPanel:(id)sender
{
[self.outputPanel showWindow:sender];
}
- (IBAction)showPreviewWindow:(id)sender
{
[self.mainController showPreviewWindow:sender];
}
/**
* Shows main window.
*/
- (IBAction)showMainWindow:(id)sender
{
[self.mainController showWindow:sender];
}
- (IBAction)openHomepage:(id)sender
{
[NSWorkspace.sharedWorkspace openURL:[NSURL URLWithString:@"https://handbrake.fr/"]];
}
- (IBAction)openForums:(id)sender
{
[NSWorkspace.sharedWorkspace openURL:[NSURL URLWithString:@"https://forum.handbrake.fr/"]];
}
- (IBAction)openUserGuide:(id)sender
{
[NSWorkspace.sharedWorkspace openURL:HBUtilities.documentationURL];
}
@end
@interface NSApplication (TouchBar)
@end
@implementation NSApplication (TouchBar)
static NSTouchBarItemIdentifier HBTouchBarMain = @"fr.handbrake.appDelegateTouchBar";
static NSTouchBarItemIdentifier HBTouchBarOpen = @"fr.handbrake.openSource";
- (NSTouchBar *)makeTouchBar
{
NSTouchBar *bar = [[NSTouchBar alloc] init];
bar.delegate = self;
bar.defaultItemIdentifiers = @[HBTouchBarOpen];
return bar;
}
- (NSTouchBarItem *)touchBar:(NSTouchBar *)touchBar makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier
{
if ([identifier isEqualTo:HBTouchBarOpen])
{
NSCustomTouchBarItem *item = [[NSCustomTouchBarItem alloc] initWithIdentifier:identifier];
item.customizationLabel = NSLocalizedString(@"Open Source", @"Touch bar");
NSButton *button = [NSButton buttonWithTitle:NSLocalizedString(@"Open Source", @"Touch bar") target:nil action:@selector(browseSources:)];
item.view = button;
return item;
}
return nil;
}
@end