123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209 |
- #import <CoreServices/CoreServices.h>
- #import <AppKit/AppKit.h>
- #import <Security/Security.h>
- #import "AppDelegate.h"
- #import "app_darwin.h"
- @interface AppDelegate () <NSToolbarDelegate>
- @property (strong, nonatomic) NSStatusItem *statusItem;
- @property (strong) NSWindow *settingsWindow;
- @end
- @implementation AppDelegate
- - (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
- // Ask to move to applications directory
- askToMoveToApplications();
- // Once in the desired directory, offer to create a symlink
- // TODO (jmorganca): find a way to provide more context to the
- // user about what this is doing, and ideally use Touch ID.
- // or add an alias in the current shell environment,
- // which wouldn't require any special privileges
- dispatch_async(dispatch_get_main_queue(), ^{
- createSymlinkWithAuthorization();
- });
- // show status menu
- NSMenu *menu = [[NSMenu alloc] init];
- NSMenuItem *openSettingsItem = [[NSMenuItem alloc] initWithTitle:@"Settings..." action:@selector(openSettingsWindow) keyEquivalent:@""];
- [menu addItem:openSettingsItem];
- [menu addItem:[NSMenuItem separatorItem]];
- [menu addItemWithTitle:@"Quit Ollama" action:@selector(quit) keyEquivalent:@"q"];
- self.statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength];
- self.statusItem.menu = menu;
- NSImage *statusImage = [NSImage imageNamed:@"icon"];
- [statusImage setTemplate:YES];
- self.statusItem.button.image = statusImage;
- }
- - (void)openSettingsWindow {
- if (!self.settingsWindow) {
- // Create the settings window centered on the screen
- self.settingsWindow = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 420, 460)
- styleMask:(NSWindowStyleMaskTitled | NSClosableWindowMask | NSWindowStyleMaskFullSizeContentView)
- backing:NSBackingStoreBuffered
- defer:NO];
- [self.settingsWindow setTitle:@"Settings"];
- [self.settingsWindow makeKeyAndOrderFront:nil];
- [self.settingsWindow center];
- // Create and configure the toolbar
- NSToolbar *toolbar = [[NSToolbar alloc] initWithIdentifier:@"SettingsToolbar"];
- toolbar.delegate = self;
- // toolbar.showsBaselineSeparator
- toolbar.displayMode = NSToolbarDisplayModeIconAndLabel;
- self.settingsWindow.toolbar = toolbar;
- self.settingsWindow.toolbarStyle = NSWindowToolbarStylePreference;
- // Necessary to make the toolbar display immediately
- [self.settingsWindow makeKeyAndOrderFront:nil];
- } else {
- [self.settingsWindow makeKeyAndOrderFront:nil];
- }
- }
- #pragma mark - NSToolbarDelegate
- - (NSToolbarItem *)toolbar:(NSToolbar *)toolbar itemForItemIdentifier:(NSToolbarItemIdentifier)itemIdentifier willBeInsertedIntoToolbar:(BOOL)flag {
- NSToolbarItem *toolbarItem = [[NSToolbarItem alloc] initWithItemIdentifier:itemIdentifier];
- if ([itemIdentifier isEqualToString:@"General"]) {
- toolbarItem.label = @"General";
- toolbarItem.paletteLabel = @"General";
- toolbarItem.toolTip = @"General Settings";
- toolbarItem.image = [NSImage imageWithSystemSymbolName:@"gear" accessibilityDescription:nil]; // Monochrome symbol
- toolbarItem.target = self;
- toolbarItem.action = @selector(switchTabs:);
- }
- if ([itemIdentifier isEqualToString:@"Networking"]) {
- toolbarItem.label = @"Networking";
- toolbarItem.paletteLabel = @"Networking";
- toolbarItem.toolTip = @"Networking Settings";
- toolbarItem.image = [NSImage imageWithSystemSymbolName:@"network" accessibilityDescription:nil]; // Monochrome symbol
- toolbarItem.target = self;
- toolbarItem.action = @selector(switchTabs:);
- }
- // Create other items with their respective images and selectors here...
- return toolbarItem;
- }
- - (NSArray<NSToolbarItemIdentifier> *)toolbarAllowedItemIdentifiers:(NSToolbar *)toolbar {
- return @[
- NSToolbarFlexibleSpaceItemIdentifier,
- @"General",
- @"Networking",
- NSToolbarFlexibleSpaceItemIdentifier
- ];
- }
- - (NSArray<NSToolbarItemIdentifier> *)toolbarDefaultItemIdentifiers:(NSToolbar *)toolbar {
- return @[
- NSToolbarFlexibleSpaceItemIdentifier,
- @"General",
- @"Networking",
- NSToolbarFlexibleSpaceItemIdentifier
- ];
- }
- - (void)switchTabs:(NSToolbarItem *)sender {
- // Your code to switch tabs based on the sender's identifier
- }
- - (void)quit {
- Quit();
- }
- @end
- int askToMoveToApplications() {
- NSString *bundlePath = [[NSBundle mainBundle] bundlePath];
- if ([bundlePath hasPrefix:@"/Applications"]) {
- return 0;
- }
- NSAlert *alert = [[NSAlert alloc] init];
- [alert setMessageText:@"Move to Applications?"];
- [alert setInformativeText:@"Ollama works best when run from the Applications directory."];
- [alert addButtonWithTitle:@"Move to Applications"];
- [alert addButtonWithTitle:@"Don't move"];
-
- [NSApp activateIgnoringOtherApps:YES];
- if ([alert runModal] != NSAlertFirstButtonReturn) {
- return 0;
- }
- // move to applications
- NSString *applicationsPath = @"/Applications";
- NSString *newPath = [applicationsPath stringByAppendingPathComponent:@"Ollama.app"];
- NSFileManager *fileManager = [NSFileManager defaultManager];
- // Check if the newPath already exists
- if ([fileManager fileExistsAtPath:newPath]) {
- NSError *removeError = nil;
- [fileManager removeItemAtPath:newPath error:&removeError];
- if (removeError) {
- NSLog(@"Error removing file at %@: %@", newPath, removeError);
- return -1; // or handle the error
- }
- }
- NSError *moveError = nil;
- [fileManager moveItemAtPath:bundlePath toPath:newPath error:&moveError];
- if (moveError) {
- NSLog(@"Error moving file from %@ to %@: %@", bundlePath, newPath, moveError);
- return -1; // or handle the error
- }
- NSLog(@"Opening %@", newPath);
- NSError *error = nil;
- NSWorkspace *workspace = [NSWorkspace sharedWorkspace];
- [workspace launchApplicationAtURL:[NSURL fileURLWithPath:newPath]
- options:NSWorkspaceLaunchNewInstance | NSWorkspaceLaunchDefault
- configuration:@{}
- error:&error];
- return 0;
- }
- int createSymlinkWithAuthorization() {
- NSString *linkPath = @"/usr/local/bin/ollama";
- NSError *error = nil;
- NSFileManager *fileManager = [NSFileManager defaultManager];
- NSString *symlinkPath = [fileManager destinationOfSymbolicLinkAtPath:linkPath error:&error];
- NSString *bundlePath = [[NSBundle mainBundle] bundlePath];
- NSString *execPath = [[NSBundle mainBundle] executablePath];
- NSString *resPath = [[NSBundle mainBundle] pathForResource:@"ollama" ofType:nil];
- // if the symlink already exists and points to the right place, don't prompt
- if ([symlinkPath isEqualToString:resPath]) {
- return 0;
- }
- OSStatus status;
- AuthorizationRef authorizationRef;
- status = AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment, kAuthorizationFlagDefaults, &authorizationRef);
- if (status != errAuthorizationSuccess) {
- NSLog(@"Failed to create authorization");
- return -1;
- }
- const char *toolPath = "/bin/ln";
- const char *args[] = {"-s", "-F", [resPath UTF8String], "/usr/local/bin/ollama", NULL};
- FILE *pipe = NULL;
- status = AuthorizationExecuteWithPrivileges(authorizationRef, toolPath, kAuthorizationFlagDefaults, (char *const *)args, &pipe);
- if (status != errAuthorizationSuccess) {
- NSLog(@"Failed to create symlink");
- return -1;
- }
- AuthorizationFree(authorizationRef, kAuthorizationFlagDestroyRights);
- return 0;
- }
|