From 91ca414d51db9cf9f07b029c5978a7423c864d63 Mon Sep 17 00:00:00 2001 From: Louie Date: Tue, 10 Feb 2026 20:31:36 -0500 Subject: [PATCH] new version --- MacNewFile/AppDelegate.m | 172 +++++++++++++++ MacNewFile/MacNewFile.entitlements | 7 +- MacNewFileFinderExtension/FinderSync.m | 201 +++++++++++------- .../MacNewFileFinderExtension.entitlements | 4 + 4 files changed, 309 insertions(+), 75 deletions(-) diff --git a/MacNewFile/AppDelegate.m b/MacNewFile/AppDelegate.m index 020bc10..1b01f69 100644 --- a/MacNewFile/AppDelegate.m +++ b/MacNewFile/AppDelegate.m @@ -8,8 +8,24 @@ #import "AppDelegate.h" #import +static NSString * const kAppGroupIdentifier = @"group.com.louieyin.MacNewFile"; + +// Feature keys for UserDefaults +static NSString * const kFeatureCopyPath = @"feature_copy_path"; +static NSString * const kFeatureTextFile = @"feature_text_file"; +static NSString * const kFeatureMarkdownFile = @"feature_markdown_file"; +static NSString * const kFeatureWordDocument = @"feature_word_document"; +static NSString * const kFeatureExcelSpreadsheet = @"feature_excel_spreadsheet"; +static NSString * const kFeaturePowerPointPresentation = @"feature_powerpoint_presentation"; +static NSString * const kFeaturePagesDocument = @"feature_pages_document"; +static NSString * const kFeatureNumbersSpreadsheet = @"feature_numbers_spreadsheet"; +static NSString * const kFeatureKeynotePresentation = @"feature_keynote_presentation"; +static NSString * const kFeatureOpenTerminal = @"feature_open_terminal"; + @interface AppDelegate () @property (strong) NSStatusItem *statusItem; +@property (strong) NSWindow *settingsWindow; +@property (strong) NSUserDefaults *sharedDefaults; @end @@ -35,6 +51,7 @@ - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { NSMenu *menu = [[NSMenu alloc] init]; [menu addItemWithTitle:@"MacNewFile is running" action:nil keyEquivalent:@""]; [menu addItem:[NSMenuItem separatorItem]]; + [menu addItemWithTitle:@"Settings" action:@selector(openSettings:) keyEquivalent:@""]; [menu addItemWithTitle:@"Quit" action:@selector(terminate:) keyEquivalent:@"q"]; self.statusItem.menu = menu; @@ -58,6 +75,161 @@ - (void)applicationWillTerminate:(NSNotification *)aNotification { [self setExtensionEnabled:NO]; } +- (void)openSettings:(id)sender { + // If window already exists, just bring it to front + if (self.settingsWindow) { + [self.settingsWindow makeKeyAndOrderFront:nil]; + [NSApp activateIgnoringOtherApps:YES]; + return; + } + + // Create the settings window + NSRect frame = NSMakeRect(0, 0, 550, 310); + NSWindowStyleMask style = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable; + self.settingsWindow = [[NSWindow alloc] initWithContentRect:frame + styleMask:style + backing:NSBackingStoreBuffered + defer:NO]; + + self.settingsWindow.title = @"MacNewFile Settings"; + self.settingsWindow.releasedWhenClosed = NO; + + NSView *contentView = self.settingsWindow.contentView; + + // Add header at the top + NSTextField *header = [NSTextField labelWithString:@"Customize your Finder right-click menu."]; + header.frame = NSMakeRect(20, frame.size.height - 35, frame.size.width - 40, 20); + header.font = [NSFont boldSystemFontOfSize:15]; + [contentView addSubview:header]; + + // Add subtitle below header + NSTextField *subtitle = [NSTextField labelWithString:@"Toggle any features you wish to add/remove."]; + subtitle.frame = NSMakeRect(20, frame.size.height - 55, frame.size.width - 40, 20); + subtitle.font = [NSFont systemFontOfSize:13]; + subtitle.textColor = [NSColor secondaryLabelColor]; + [contentView addSubview:subtitle]; + + // Add divider line + NSBox *divider = [[NSBox alloc] initWithFrame:NSMakeRect(20, frame.size.height - 65, frame.size.width - 40, 1)]; + divider.boxType = NSBoxCustom; + divider.borderColor = [NSColor separatorColor]; + divider.borderWidth = 1; + [contentView addSubview:divider]; + + // Initialize shared defaults + self.sharedDefaults = [[NSUserDefaults alloc] initWithSuiteName:kAppGroupIdentifier]; + + CGFloat checkboxHeight = 30; + CGFloat columnGap = 30; + CGFloat columnWidth = (frame.size.width - 40 - columnGap) / 2; + CGFloat leftX = 20; + CGFloat rightX = 20 + columnWidth + columnGap; + CGFloat currentY = frame.size.height - 105; + + // Add Copy Path checkbox (left column) + NSButton *copyPathCheckbox = [NSButton checkboxWithTitle:@"Copy Path" target:self action:@selector(checkboxToggled:)]; + copyPathCheckbox.identifier = kFeatureCopyPath; + copyPathCheckbox.frame = NSMakeRect(leftX, currentY, columnWidth, checkboxHeight); + id copyPathValue = [self.sharedDefaults objectForKey:kFeatureCopyPath]; + if (copyPathValue == nil) { + copyPathCheckbox.state = NSControlStateValueOn; + [self.sharedDefaults setBool:YES forKey:kFeatureCopyPath]; + } else { + copyPathCheckbox.state = [self.sharedDefaults boolForKey:kFeatureCopyPath] ? NSControlStateValueOn : NSControlStateValueOff; + } + [contentView addSubview:copyPathCheckbox]; + + // Add Open Terminal checkbox (right column, same row as Copy Path) + NSButton *openTerminalCheckbox = [NSButton checkboxWithTitle:@"Open Terminal" target:self action:@selector(checkboxToggled:)]; + openTerminalCheckbox.identifier = kFeatureOpenTerminal; + openTerminalCheckbox.frame = NSMakeRect(rightX, currentY, columnWidth, checkboxHeight); + id openTerminalValue = [self.sharedDefaults objectForKey:kFeatureOpenTerminal]; + if (openTerminalValue == nil) { + openTerminalCheckbox.state = NSControlStateValueOn; + [self.sharedDefaults setBool:YES forKey:kFeatureOpenTerminal]; + } else { + openTerminalCheckbox.state = [self.sharedDefaults boolForKey:kFeatureOpenTerminal] ? NSControlStateValueOn : NSControlStateValueOff; + } + [contentView addSubview:openTerminalCheckbox]; + + // Add second divider below Copy Path + currentY -= 35; + NSBox *divider2 = [[NSBox alloc] initWithFrame:NSMakeRect(20, currentY + 25, frame.size.width - 40, 1)]; + divider2.boxType = NSBoxCustom; + divider2.borderColor = [NSColor separatorColor]; + divider2.borderWidth = 1; + [contentView addSubview:divider2]; + + // File type features (2 columns) + NSArray *fileFeatures = @[ + @{@"name": @"Text File", @"key": kFeatureTextFile}, + @{@"name": @"Markdown File", @"key": kFeatureMarkdownFile}, + @{@"name": @"Microsoft Word Document", @"key": kFeatureWordDocument}, + @{@"name": @"Pages Document", @"key": kFeaturePagesDocument}, + @{@"name": @"Microsoft Excel Spreadsheet", @"key": kFeatureExcelSpreadsheet}, + @{@"name": @"Numbers Spreadsheet", @"key": kFeatureNumbersSpreadsheet}, + @{@"name": @"Microsoft PowerPoint Presentation", @"key": kFeaturePowerPointPresentation}, + @{@"name": @"Keynote Presentation", @"key": kFeatureKeynotePresentation}, + ]; + + currentY -= 15; + + for (NSUInteger i = 0; i < fileFeatures.count; i++) { + NSDictionary *feature = fileFeatures[i]; + NSString *name = feature[@"name"]; + NSString *key = feature[@"key"]; + + NSButton *checkbox = [NSButton checkboxWithTitle:name target:self action:@selector(checkboxToggled:)]; + checkbox.identifier = key; + + NSUInteger column = i % 2; + NSUInteger row = i / 2; + CGFloat xPos = (column == 0) ? leftX : rightX; + CGFloat yPos = currentY - (row * checkboxHeight); + + checkbox.frame = NSMakeRect(xPos, yPos, columnWidth, checkboxHeight); + + // Load saved state (default to ON if not set) + id savedValue = [self.sharedDefaults objectForKey:key]; + if (savedValue == nil) { + checkbox.state = NSControlStateValueOn; + [self.sharedDefaults setBool:YES forKey:key]; + } else { + checkbox.state = [self.sharedDefaults boolForKey:key] ? NSControlStateValueOn : NSControlStateValueOff; + } + + [contentView addSubview:checkbox]; + } + + [self.sharedDefaults synchronize]; + + // Add Save button in bottom right corner + NSButton *saveButton = [NSButton buttonWithTitle:@"Save" target:self action:@selector(saveSettings:)]; + saveButton.bezelStyle = NSBezelStyleRounded; + saveButton.keyEquivalent = @"\r"; + saveButton.frame = NSMakeRect(frame.size.width - 100, 20, 80, 30); + [contentView addSubview:saveButton]; + + // Center the window on screen + [self.settingsWindow center]; + + // Show the window and bring app to front + [self.settingsWindow makeKeyAndOrderFront:nil]; + [NSApp activateIgnoringOtherApps:YES]; +} + +- (void)saveSettings:(id)sender { + [self.settingsWindow close]; +} + +- (void)checkboxToggled:(NSButton *)sender { + NSString *key = sender.identifier; + BOOL isEnabled = (sender.state == NSControlStateValueOn); + [self.sharedDefaults setBool:isEnabled forKey:key]; + [self.sharedDefaults synchronize]; + NSLog(@"Feature %@ set to %@", key, isEnabled ? @"ON" : @"OFF"); +} + - (void)setExtensionEnabled:(BOOL)enabled { NSTask *task = [[NSTask alloc] init]; task.launchPath = @"/usr/bin/pluginkit"; diff --git a/MacNewFile/MacNewFile.entitlements b/MacNewFile/MacNewFile.entitlements index 0c67376..6396311 100644 --- a/MacNewFile/MacNewFile.entitlements +++ b/MacNewFile/MacNewFile.entitlements @@ -1,5 +1,10 @@ - + + com.apple.security.application-groups + + group.com.louieyin.MacNewFile + + diff --git a/MacNewFileFinderExtension/FinderSync.m b/MacNewFileFinderExtension/FinderSync.m index f85ad08..96972ed 100644 --- a/MacNewFileFinderExtension/FinderSync.m +++ b/MacNewFileFinderExtension/FinderSync.m @@ -7,11 +7,32 @@ #import "FinderSync.h" +static NSString * const kAppGroupIdentifier = @"group.com.louieyin.MacNewFile"; + +// Feature keys for UserDefaults +static NSString * const kFeatureCopyPath = @"feature_copy_path"; +static NSString * const kFeatureTextFile = @"feature_text_file"; +static NSString * const kFeatureMarkdownFile = @"feature_markdown_file"; +static NSString * const kFeatureWordDocument = @"feature_word_document"; +static NSString * const kFeatureExcelSpreadsheet = @"feature_excel_spreadsheet"; +static NSString * const kFeaturePowerPointPresentation = @"feature_powerpoint_presentation"; +static NSString * const kFeaturePagesDocument = @"feature_pages_document"; +static NSString * const kFeatureNumbersSpreadsheet = @"feature_numbers_spreadsheet"; +static NSString * const kFeatureKeynotePresentation = @"feature_keynote_presentation"; +static NSString * const kFeatureOpenTerminal = @"feature_open_terminal"; + +@interface FinderSync () +@property (strong) NSUserDefaults *sharedDefaults; +@end + @implementation FinderSync - (instancetype)init { self = [super init]; + // Initialize shared defaults + self.sharedDefaults = [[NSUserDefaults alloc] initWithSuiteName:kAppGroupIdentifier]; + // Monitor root filesystem - covers all local directories // Note: iCloud Drive is not supported due to macOS Sonoma+ limitations [FIFinderSyncController defaultController].directoryURLs = [NSSet setWithObject:[NSURL fileURLWithPath:@"/"]]; @@ -19,6 +40,15 @@ - (instancetype)init { return self; } +- (BOOL)isFeatureEnabled:(NSString *)key { + // Default to YES if not set + id value = [self.sharedDefaults objectForKey:key]; + if (value == nil) { + return YES; + } + return [self.sharedDefaults boolForKey:key]; +} + #pragma mark - Primary Finder Sync protocol methods - (void)beginObservingDirectoryAtURL:(NSURL *)url { @@ -51,85 +81,107 @@ - (NSImage *)toolbarItemImage { - (NSMenu *)menuForMenuKind:(FIMenuKind)whichMenu { NSMenu *menu = [[NSMenu alloc] initWithTitle:@""]; - // Add "Copy Path" menu item - NSMenuItem *copyPathItem = [[NSMenuItem alloc] initWithTitle:@"Copy Path" action:@selector(copyPathToClipboard:) keyEquivalent:@""]; - NSImage *copyIcon = [NSImage imageNamed:@"copy"]; - copyIcon.template = YES; - copyPathItem.image = copyIcon; - [menu addItem:copyPathItem]; + // Add "Copy Path" menu item (if enabled) + if ([self isFeatureEnabled:kFeatureCopyPath]) { + NSMenuItem *copyPathItem = [[NSMenuItem alloc] initWithTitle:@"Copy Path" action:@selector(copyPathToClipboard:) keyEquivalent:@""]; + NSImage *copyIcon = [NSImage imageNamed:@"copy"]; + copyIcon.template = YES; + copyPathItem.image = copyIcon; + [menu addItem:copyPathItem]; + } - // Add "Open Terminal" menu item - NSMenuItem *openTerminalItem = [[NSMenuItem alloc] initWithTitle:@"Open New Terminal" action:@selector(openTerminalAtPath:) keyEquivalent:@""]; - NSImage *terminalIcon = [NSImage imageNamed:@"terminal"]; - terminalIcon.template = YES; - openTerminalItem.image = terminalIcon; - [menu addItem:openTerminalItem]; + // Add "Open Terminal" menu item (if enabled) + if ([self isFeatureEnabled:kFeatureOpenTerminal]) { + NSMenuItem *openTerminalItem = [[NSMenuItem alloc] initWithTitle:@"Open New Terminal" action:@selector(openTerminalAtPath:) keyEquivalent:@""]; + NSImage *terminalIcon = [NSImage imageNamed:@"terminal"]; + terminalIcon.template = YES; + openTerminalItem.image = terminalIcon; + [menu addItem:openTerminalItem]; + } // Create submenu for New File options NSMenu *submenu = [[NSMenu alloc] initWithTitle:@""]; - // Add "New Text File" to submenu - NSMenuItem *newTextItem = [[NSMenuItem alloc] initWithTitle:@"Text File" action:@selector(createNewTextFile:) keyEquivalent:@""]; - NSImage *textIcon = [NSImage imageNamed:@"edit"]; - textIcon.template = YES; - newTextItem.image = textIcon; - [submenu addItem:newTextItem]; - - // Add "New Markdown File" to submenu - NSMenuItem *newMarkdownItem = [[NSMenuItem alloc] initWithTitle:@"Markdown File" action:@selector(createNewMarkdownFile:) keyEquivalent:@""]; - NSImage *markdownIcon = [NSImage imageNamed:@"document"]; - markdownIcon.template = YES; - newMarkdownItem.image = markdownIcon; - [submenu addItem:newMarkdownItem]; - - // Add "New Microsoft Word Document" to submenu - NSMenuItem *newWordItem = [[NSMenuItem alloc] initWithTitle:@"Microsoft Word Document" action:@selector(createNewWordDocument:) keyEquivalent:@""]; - NSImage *wordIcon = [NSImage imageNamed:@"word"]; - wordIcon.template = YES; - newWordItem.image = wordIcon; - [submenu addItem:newWordItem]; - - // Add "New Microsoft Excel Spreadsheet" to submenu - NSMenuItem *newExcelItem = [[NSMenuItem alloc] initWithTitle:@"Microsoft Excel Spreadsheet" action:@selector(createNewExcelDocument:) keyEquivalent:@""]; - NSImage *excelIcon = [NSImage imageNamed:@"excel"]; - excelIcon.template = YES; - newExcelItem.image = excelIcon; - [submenu addItem:newExcelItem]; - - // Add "New Microsoft PowerPoint Presentation" to submenu - NSMenuItem *newPowerPointItem = [[NSMenuItem alloc] initWithTitle:@"Microsoft PowerPoint Presentation" action:@selector(createNewPowerPointDocument:) keyEquivalent:@""]; - NSImage *powerPointIcon = [NSImage imageNamed:@"powerpoint"]; - powerPointIcon.template = YES; - newPowerPointItem.image = powerPointIcon; - [submenu addItem:newPowerPointItem]; - - // Add "New Pages Document" to submenu - NSMenuItem *newPagesItem = [[NSMenuItem alloc] initWithTitle:@"Pages Document" action:@selector(createNewPagesDocument:) keyEquivalent:@""]; - NSImage *pagesIcon = [NSImage imageNamed:@"pages"]; - pagesIcon.template = YES; - newPagesItem.image = pagesIcon; - [submenu addItem:newPagesItem]; - - // Add "New Numbers Spreadsheet" to submenu - NSMenuItem *newNumbersItem = [[NSMenuItem alloc] initWithTitle:@"Numbers Spreadsheet" action:@selector(createNewNumbersDocument:) keyEquivalent:@""]; - NSImage *numbersIcon = [NSImage imageNamed:@"numbers"]; - numbersIcon.template = YES; - newNumbersItem.image = numbersIcon; - [submenu addItem:newNumbersItem]; - - // Add "New Keynote Presentation" to submenu - NSMenuItem *newKeynoteItem = [[NSMenuItem alloc] initWithTitle:@"Keynote Presentation" action:@selector(createNewKeynoteDocument:) keyEquivalent:@""]; - NSImage *keynoteIcon = [NSImage imageNamed:@"keynote"]; - keynoteIcon.template = YES; - newKeynoteItem.image = keynoteIcon; - [submenu addItem:newKeynoteItem]; - - // Add "New File" submenu - NSMenuItem *mainItem = [[NSMenuItem alloc] initWithTitle:@"New File" action:nil keyEquivalent:@""]; - NSImage *mainIcon = [NSImage imageNamed:@"add"]; - mainItem.image = mainIcon; - mainItem.submenu = submenu; - [menu addItem:mainItem]; + // Add "New Text File" to submenu (if enabled) + if ([self isFeatureEnabled:kFeatureTextFile]) { + NSMenuItem *newTextItem = [[NSMenuItem alloc] initWithTitle:@"Text File" action:@selector(createNewTextFile:) keyEquivalent:@""]; + NSImage *textIcon = [NSImage imageNamed:@"edit"]; + textIcon.template = YES; + newTextItem.image = textIcon; + [submenu addItem:newTextItem]; + } + + // Add "New Markdown File" to submenu (if enabled) + if ([self isFeatureEnabled:kFeatureMarkdownFile]) { + NSMenuItem *newMarkdownItem = [[NSMenuItem alloc] initWithTitle:@"Markdown File" action:@selector(createNewMarkdownFile:) keyEquivalent:@""]; + NSImage *markdownIcon = [NSImage imageNamed:@"document"]; + markdownIcon.template = YES; + newMarkdownItem.image = markdownIcon; + [submenu addItem:newMarkdownItem]; + } + + // Add "New Microsoft Word Document" to submenu (if enabled) + if ([self isFeatureEnabled:kFeatureWordDocument]) { + NSMenuItem *newWordItem = [[NSMenuItem alloc] initWithTitle:@"Microsoft Word Document" action:@selector(createNewWordDocument:) keyEquivalent:@""]; + NSImage *wordIcon = [NSImage imageNamed:@"word"]; + wordIcon.template = YES; + newWordItem.image = wordIcon; + [submenu addItem:newWordItem]; + } + + // Add "New Microsoft Excel Spreadsheet" to submenu (if enabled) + if ([self isFeatureEnabled:kFeatureExcelSpreadsheet]) { + NSMenuItem *newExcelItem = [[NSMenuItem alloc] initWithTitle:@"Microsoft Excel Spreadsheet" action:@selector(createNewExcelDocument:) keyEquivalent:@""]; + NSImage *excelIcon = [NSImage imageNamed:@"excel"]; + excelIcon.template = YES; + newExcelItem.image = excelIcon; + [submenu addItem:newExcelItem]; + } + + // Add "New Microsoft PowerPoint Presentation" to submenu (if enabled) + if ([self isFeatureEnabled:kFeaturePowerPointPresentation]) { + NSMenuItem *newPowerPointItem = [[NSMenuItem alloc] initWithTitle:@"Microsoft PowerPoint Presentation" action:@selector(createNewPowerPointDocument:) keyEquivalent:@""]; + NSImage *powerPointIcon = [NSImage imageNamed:@"powerpoint"]; + powerPointIcon.template = YES; + newPowerPointItem.image = powerPointIcon; + [submenu addItem:newPowerPointItem]; + } + + // Add "New Pages Document" to submenu (if enabled) + if ([self isFeatureEnabled:kFeaturePagesDocument]) { + NSMenuItem *newPagesItem = [[NSMenuItem alloc] initWithTitle:@"Pages Document" action:@selector(createNewPagesDocument:) keyEquivalent:@""]; + NSImage *pagesIcon = [NSImage imageNamed:@"pages"]; + pagesIcon.template = YES; + newPagesItem.image = pagesIcon; + [submenu addItem:newPagesItem]; + } + + // Add "New Numbers Spreadsheet" to submenu (if enabled) + if ([self isFeatureEnabled:kFeatureNumbersSpreadsheet]) { + NSMenuItem *newNumbersItem = [[NSMenuItem alloc] initWithTitle:@"Numbers Spreadsheet" action:@selector(createNewNumbersDocument:) keyEquivalent:@""]; + NSImage *numbersIcon = [NSImage imageNamed:@"numbers"]; + numbersIcon.template = YES; + newNumbersItem.image = numbersIcon; + [submenu addItem:newNumbersItem]; + } + + // Add "New Keynote Presentation" to submenu (if enabled) + if ([self isFeatureEnabled:kFeatureKeynotePresentation]) { + NSMenuItem *newKeynoteItem = [[NSMenuItem alloc] initWithTitle:@"Keynote Presentation" action:@selector(createNewKeynoteDocument:) keyEquivalent:@""]; + NSImage *keynoteIcon = [NSImage imageNamed:@"keynote"]; + keynoteIcon.template = YES; + newKeynoteItem.image = keynoteIcon; + [submenu addItem:newKeynoteItem]; + } + + // Only add "New File" menu if there are items in the submenu + if (submenu.numberOfItems > 0) { + NSMenuItem *mainItem = [[NSMenuItem alloc] initWithTitle:@"New File" action:nil keyEquivalent:@""]; + NSImage *mainIcon = [NSImage imageNamed:@"add"]; + mainItem.image = mainIcon; + mainItem.submenu = submenu; + [menu addItem:mainItem]; + } return menu; } @@ -557,3 +609,4 @@ - (IBAction)sampleAction:(id)sender { } @end + diff --git a/MacNewFileFinderExtension/MacNewFileFinderExtension.entitlements b/MacNewFileFinderExtension/MacNewFileFinderExtension.entitlements index 4b3a63e..7d61eb9 100644 --- a/MacNewFileFinderExtension/MacNewFileFinderExtension.entitlements +++ b/MacNewFileFinderExtension/MacNewFileFinderExtension.entitlements @@ -4,6 +4,10 @@ com.apple.security.app-sandbox + com.apple.security.application-groups + + group.com.louieyin.MacNewFile + com.apple.security.automation.apple-events com.apple.security.temporary-exception.apple-events