123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282 |
- #import <AppKit/AppKit.h>
- #import <Cocoa/Cocoa.h>
- #import <CoreServices/CoreServices.h>
- #import <Security/Security.h>
- #import <ServiceManagement/ServiceManagement.h>
- #import "app_darwin.h"
- @interface AppDelegate ()
- @property (strong, nonatomic) NSStatusItem *statusItem;
- @end
- @implementation AppDelegate
- - (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
- // show status menu
- NSMenu *menu = [[NSMenu alloc] init];
- NSMenuItem *aboutMenuItem = [[NSMenuItem alloc] initWithTitle:@"About Ollama" action:@selector(aboutOllama) keyEquivalent:@""];
- [aboutMenuItem setTarget:self];
- [menu addItem:aboutMenuItem];
- // Settings submenu
- NSMenu *settingsMenu = [[NSMenu alloc] initWithTitle:@"Settings"];
- // Submenu items
- NSMenuItem *chooseModelDirectoryItem = [[NSMenuItem alloc] initWithTitle:@"Choose model directory..." action:@selector(chooseModelDirectory) keyEquivalent:@""];
- [chooseModelDirectoryItem setTarget:self];
- [chooseModelDirectoryItem setEnabled:YES];
- [settingsMenu addItem:chooseModelDirectoryItem];
- NSMenuItem *exposeExternallyItem = [[NSMenuItem alloc] initWithTitle:@"Allow external connections" action:@selector(toggleExposeExternally:) keyEquivalent:@""];
- [exposeExternallyItem setTarget:self];
- [exposeExternallyItem setState:NSOffState]; // Set initial state to off
- [exposeExternallyItem setEnabled:YES];
- [settingsMenu addItem:exposeExternallyItem];
- NSMenuItem *allowCrossOriginItem = [[NSMenuItem alloc] initWithTitle:@"Allow browser requests" action:@selector(toggleCrossOrigin:) keyEquivalent:@""];
- [allowCrossOriginItem setTarget:self];
- [allowCrossOriginItem setState:NSOffState]; // Set initial state to off
- [allowCrossOriginItem setEnabled:YES];
- [settingsMenu addItem:allowCrossOriginItem];
- NSMenuItem *settingsMenuItem = [[NSMenuItem alloc] initWithTitle:@"Settings" action:nil keyEquivalent:@""];
- [settingsMenuItem setSubmenu:settingsMenu];
- [menu addItem:settingsMenuItem];
- [menu addItemWithTitle:@"Quit Ollama" action:@selector(quit) keyEquivalent:@"q"];
- self.statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength];
- [self.statusItem addObserver:self forKeyPath:@"button.effectiveAppearance" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionInitial context:nil];
- self.statusItem.menu = menu;
- [self showIcon];
- }
- - (void)aboutOllama {
- [[NSApplication sharedApplication] orderFrontStandardAboutPanel:nil];
- }
- - (void)toggleCrossOrigin:(id)sender {
- NSMenuItem *item = (NSMenuItem *)sender;
- if ([item state] == NSOffState) {
- // Do something when cross-origin requests are allowed
- [item setState:NSOnState];
- } else {
- // Do something when cross-origin requests are disallowed
- [item setState:NSOffState];
- }
- }
- - (void)toggleExposeExternally:(id)sender {
- NSMenuItem *item = (NSMenuItem *)sender;
- if ([item state] == NSOffState) {
- // Do something when Ollama is exposed externally
- [item setState:NSOnState];
- } else {
- // Do something when Ollama is not exposed externally
- [item setState:NSOffState];
- }
- }
- - (void)chooseModelDirectory {
- NSOpenPanel *openPanel = [NSOpenPanel openPanel];
- [openPanel setCanChooseFiles:NO];
- [openPanel setCanChooseDirectories:YES];
- [openPanel setAllowsMultipleSelection:NO];
- NSInteger result = [openPanel runModal];
- if (result == NSModalResponseOK) {
- NSURL *selectedDirectoryURL = [openPanel URLs].firstObject;
- // Do something with the selected directory URL
- }
- }
- -(void) showIcon {
- NSAppearance* appearance = self.statusItem.button.effectiveAppearance;
- NSString* appearanceName = (NSString*)(appearance.name);
- NSString* iconName = [[appearanceName lowercaseString] containsString:@"dark"] ? @"iconDark" : @"icon";
- NSImage* statusImage = [NSImage imageNamed:iconName];
- [statusImage setTemplate:YES];
- self.statusItem.button.image = statusImage;
- }
- -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
- [self showIcon];
- }
- - (void)quit {
- [NSApp stop:nil];
- }
- @end
- void run() {
- @autoreleasepool {
- [NSApplication sharedApplication];
- AppDelegate *appDelegate = [[AppDelegate alloc] init];
- [NSApp setDelegate:appDelegate];
- [NSApp run];
- }
- }
- // killOtherInstances kills all other instances of the app currently
- // running. This way we can ensure that only the most recently started
- // instance of Ollama is running
- void killOtherInstances() {
- pid_t pid = getpid();
- NSArray *all = [[NSWorkspace sharedWorkspace] runningApplications];
- NSMutableArray *apps = [NSMutableArray array];
- for (NSRunningApplication *app in all) {
- if ([app.bundleIdentifier isEqualToString:[[NSBundle mainBundle] bundleIdentifier]] ||
- [app.bundleIdentifier isEqualToString:@"ai.ollama.ollama"] ||
- [app.bundleIdentifier isEqualToString:@"com.electron.ollama"]) {
- if (app.processIdentifier != pid) {
- [apps addObject:app];
- }
- }
- }
- for (NSRunningApplication *app in apps) {
- kill(app.processIdentifier, SIGTERM);
- }
- NSDate *startTime = [NSDate date];
- for (NSRunningApplication *app in apps) {
- while (!app.terminated) {
- if (-[startTime timeIntervalSinceNow] >= 5) {
- kill(app.processIdentifier, SIGKILL);
- break;
- }
- [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
- }
- }
- }
- bool askToMoveToApplications() {
- NSString *bundlePath = [[NSBundle mainBundle] bundlePath];
- if ([bundlePath hasPrefix:@"/Applications"]) {
- return false;
- }
- 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 false;
- }
- // 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 false; // 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 false;
- }
- NSLog(@"Opening %@", newPath);
- NSError *error = nil;
- NSWorkspace *workspace = [NSWorkspace sharedWorkspace];
- #pragma clang diagnostic ignored "-Wdeprecated-declarations"
- [workspace launchApplicationAtURL:[NSURL fileURLWithPath:newPath]
- options:NSWorkspaceLaunchNewInstance | NSWorkspaceLaunchDefault
- configuration:@{}
- error:&error];
- return true;
- }
- int installSymlink() {
- 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]) {
- NSLog(@"symbolic link already exists and points to the right place");
- return 0;
- }
- NSString *authorizationPrompt = @"Ollama is trying to install its command line interface (CLI) tool.";
- AuthorizationRef auth = NULL;
- OSStatus createStatus = AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment, kAuthorizationFlagDefaults, &auth);
- if (createStatus != errAuthorizationSuccess) {
- NSLog(@"Error creating authorization");
- return -1;
- }
- NSString * bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];
- NSString *rightNameString = [NSString stringWithFormat:@"%@.%@", bundleIdentifier, @"auth3"];
- const char *rightName = rightNameString.UTF8String;
- OSStatus getRightResult = AuthorizationRightGet(rightName, NULL);
- if (getRightResult == errAuthorizationDenied) {
- if (AuthorizationRightSet(auth, rightName, (__bridge CFTypeRef _Nonnull)(@(kAuthorizationRuleAuthenticateAsAdmin)), (__bridge CFStringRef _Nullable)(authorizationPrompt), NULL, NULL) != errAuthorizationSuccess) {
- NSLog(@"Failed to set right");
- return -1;
- }
- }
- AuthorizationItem right = { .name = rightName, .valueLength = 0, .value = NULL, .flags = 0 };
- AuthorizationRights rights = { .count = 1, .items = &right };
- AuthorizationFlags flags = (AuthorizationFlags)(kAuthorizationFlagExtendRights | kAuthorizationFlagInteractionAllowed);
- AuthorizationItem iconAuthorizationItem = {.name = kAuthorizationEnvironmentIcon, .valueLength = 0, .value = NULL, .flags = 0};
- AuthorizationEnvironment authorizationEnvironment = {.count = 0, .items = NULL};
- BOOL failedToUseSystemDomain = NO;
- OSStatus copyStatus = AuthorizationCopyRights(auth, &rights, &authorizationEnvironment, flags, NULL);
- if (copyStatus != errAuthorizationSuccess) {
- failedToUseSystemDomain = YES;
- if (copyStatus == errAuthorizationCanceled) {
- NSLog(@"User cancelled authorization");
- return -1;
- } else {
- NSLog(@"Failed copying system domain rights: %d", copyStatus);
- return -1;
- }
- }
- const char *toolPath = "/bin/ln";
- const char *args[] = {"-s", "-F", [resPath UTF8String], "/usr/local/bin/ollama", NULL};
- FILE *pipe = NULL;
- #pragma clang diagnostic ignored "-Wdeprecated-declarations"
- OSStatus status = AuthorizationExecuteWithPrivileges(auth, toolPath, kAuthorizationFlagDefaults, (char *const *)args, &pipe);
- if (status != errAuthorizationSuccess) {
- NSLog(@"Failed to create symlink");
- return -1;
- }
- AuthorizationFree(auth, kAuthorizationFlagDestroyRights);
- return 0;
- }
|