AppDelegate.m 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. #import <CoreServices/CoreServices.h>
  2. #import <AppKit/AppKit.h>
  3. #import <Security/Security.h>
  4. #import "AppDelegate.h"
  5. #import "app_darwin.h"
  6. @interface AppDelegate () <NSToolbarDelegate>
  7. @property (strong, nonatomic) NSStatusItem *statusItem;
  8. @property (strong) NSWindow *settingsWindow;
  9. @end
  10. @implementation AppDelegate
  11. - (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
  12. // Ask to move to applications directory
  13. askToMoveToApplications();
  14. // Once in the desired directory, offer to create a symlink
  15. // TODO (jmorganca): find a way to provide more context to the
  16. // user about what this is doing, and ideally use Touch ID.
  17. // or add an alias in the current shell environment,
  18. // which wouldn't require any special privileges
  19. dispatch_async(dispatch_get_main_queue(), ^{
  20. createSymlinkWithAuthorization();
  21. });
  22. // show status menu
  23. NSMenu *menu = [[NSMenu alloc] init];
  24. NSMenuItem *openSettingsItem = [[NSMenuItem alloc] initWithTitle:@"Settings..." action:@selector(openSettingsWindow) keyEquivalent:@""];
  25. [menu addItem:openSettingsItem];
  26. [menu addItem:[NSMenuItem separatorItem]];
  27. [menu addItemWithTitle:@"Quit Ollama" action:@selector(quit) keyEquivalent:@"q"];
  28. self.statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength];
  29. self.statusItem.menu = menu;
  30. NSImage *statusImage = [NSImage imageNamed:@"icon"];
  31. [statusImage setTemplate:YES];
  32. self.statusItem.button.image = statusImage;
  33. }
  34. - (void)openSettingsWindow {
  35. if (!self.settingsWindow) {
  36. // Create the settings window centered on the screen
  37. self.settingsWindow = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 420, 460)
  38. styleMask:(NSWindowStyleMaskTitled | NSClosableWindowMask | NSWindowStyleMaskFullSizeContentView)
  39. backing:NSBackingStoreBuffered
  40. defer:NO];
  41. [self.settingsWindow setTitle:@"Settings"];
  42. [self.settingsWindow makeKeyAndOrderFront:nil];
  43. [self.settingsWindow center];
  44. // Create and configure the toolbar
  45. NSToolbar *toolbar = [[NSToolbar alloc] initWithIdentifier:@"SettingsToolbar"];
  46. toolbar.delegate = self;
  47. // toolbar.showsBaselineSeparator
  48. toolbar.displayMode = NSToolbarDisplayModeIconAndLabel;
  49. self.settingsWindow.toolbar = toolbar;
  50. self.settingsWindow.toolbarStyle = NSWindowToolbarStylePreference;
  51. // Necessary to make the toolbar display immediately
  52. [self.settingsWindow makeKeyAndOrderFront:nil];
  53. } else {
  54. [self.settingsWindow makeKeyAndOrderFront:nil];
  55. }
  56. }
  57. #pragma mark - NSToolbarDelegate
  58. - (NSToolbarItem *)toolbar:(NSToolbar *)toolbar itemForItemIdentifier:(NSToolbarItemIdentifier)itemIdentifier willBeInsertedIntoToolbar:(BOOL)flag {
  59. NSToolbarItem *toolbarItem = [[NSToolbarItem alloc] initWithItemIdentifier:itemIdentifier];
  60. if ([itemIdentifier isEqualToString:@"General"]) {
  61. toolbarItem.label = @"General";
  62. toolbarItem.paletteLabel = @"General";
  63. toolbarItem.toolTip = @"General Settings";
  64. toolbarItem.image = [NSImage imageWithSystemSymbolName:@"gear" accessibilityDescription:nil]; // Monochrome symbol
  65. toolbarItem.target = self;
  66. toolbarItem.action = @selector(switchTabs:);
  67. }
  68. if ([itemIdentifier isEqualToString:@"Networking"]) {
  69. toolbarItem.label = @"Networking";
  70. toolbarItem.paletteLabel = @"Networking";
  71. toolbarItem.toolTip = @"Networking Settings";
  72. toolbarItem.image = [NSImage imageWithSystemSymbolName:@"network" accessibilityDescription:nil]; // Monochrome symbol
  73. toolbarItem.target = self;
  74. toolbarItem.action = @selector(switchTabs:);
  75. }
  76. // Create other items with their respective images and selectors here...
  77. return toolbarItem;
  78. }
  79. - (NSArray<NSToolbarItemIdentifier> *)toolbarAllowedItemIdentifiers:(NSToolbar *)toolbar {
  80. return @[
  81. NSToolbarFlexibleSpaceItemIdentifier,
  82. @"General",
  83. @"Networking",
  84. NSToolbarFlexibleSpaceItemIdentifier
  85. ];
  86. }
  87. - (NSArray<NSToolbarItemIdentifier> *)toolbarDefaultItemIdentifiers:(NSToolbar *)toolbar {
  88. return @[
  89. NSToolbarFlexibleSpaceItemIdentifier,
  90. @"General",
  91. @"Networking",
  92. NSToolbarFlexibleSpaceItemIdentifier
  93. ];
  94. }
  95. - (void)switchTabs:(NSToolbarItem *)sender {
  96. // Your code to switch tabs based on the sender's identifier
  97. }
  98. - (void)quit {
  99. Quit();
  100. }
  101. @end
  102. int askToMoveToApplications() {
  103. NSString *bundlePath = [[NSBundle mainBundle] bundlePath];
  104. if ([bundlePath hasPrefix:@"/Applications"]) {
  105. return 0;
  106. }
  107. NSAlert *alert = [[NSAlert alloc] init];
  108. [alert setMessageText:@"Move to Applications?"];
  109. [alert setInformativeText:@"Ollama works best when run from the Applications directory."];
  110. [alert addButtonWithTitle:@"Move to Applications"];
  111. [alert addButtonWithTitle:@"Don't move"];
  112. [NSApp activateIgnoringOtherApps:YES];
  113. if ([alert runModal] != NSAlertFirstButtonReturn) {
  114. return 0;
  115. }
  116. // move to applications
  117. NSString *applicationsPath = @"/Applications";
  118. NSString *newPath = [applicationsPath stringByAppendingPathComponent:@"Ollama.app"];
  119. NSFileManager *fileManager = [NSFileManager defaultManager];
  120. // Check if the newPath already exists
  121. if ([fileManager fileExistsAtPath:newPath]) {
  122. NSError *removeError = nil;
  123. [fileManager removeItemAtPath:newPath error:&removeError];
  124. if (removeError) {
  125. NSLog(@"Error removing file at %@: %@", newPath, removeError);
  126. return -1; // or handle the error
  127. }
  128. }
  129. NSError *moveError = nil;
  130. [fileManager moveItemAtPath:bundlePath toPath:newPath error:&moveError];
  131. if (moveError) {
  132. NSLog(@"Error moving file from %@ to %@: %@", bundlePath, newPath, moveError);
  133. return -1; // or handle the error
  134. }
  135. NSLog(@"Opening %@", newPath);
  136. NSError *error = nil;
  137. NSWorkspace *workspace = [NSWorkspace sharedWorkspace];
  138. [workspace launchApplicationAtURL:[NSURL fileURLWithPath:newPath]
  139. options:NSWorkspaceLaunchNewInstance | NSWorkspaceLaunchDefault
  140. configuration:@{}
  141. error:&error];
  142. return 0;
  143. }
  144. int createSymlinkWithAuthorization() {
  145. NSString *linkPath = @"/usr/local/bin/ollama";
  146. NSError *error = nil;
  147. NSFileManager *fileManager = [NSFileManager defaultManager];
  148. NSString *symlinkPath = [fileManager destinationOfSymbolicLinkAtPath:linkPath error:&error];
  149. NSString *bundlePath = [[NSBundle mainBundle] bundlePath];
  150. NSString *execPath = [[NSBundle mainBundle] executablePath];
  151. NSString *resPath = [[NSBundle mainBundle] pathForResource:@"ollama" ofType:nil];
  152. // if the symlink already exists and points to the right place, don't prompt
  153. if ([symlinkPath isEqualToString:resPath]) {
  154. return 0;
  155. }
  156. OSStatus status;
  157. AuthorizationRef authorizationRef;
  158. status = AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment, kAuthorizationFlagDefaults, &authorizationRef);
  159. if (status != errAuthorizationSuccess) {
  160. NSLog(@"Failed to create authorization");
  161. return -1;
  162. }
  163. const char *toolPath = "/bin/ln";
  164. const char *args[] = {"-s", "-F", [resPath UTF8String], "/usr/local/bin/ollama", NULL};
  165. FILE *pipe = NULL;
  166. status = AuthorizationExecuteWithPrivileges(authorizationRef, toolPath, kAuthorizationFlagDefaults, (char *const *)args, &pipe);
  167. if (status != errAuthorizationSuccess) {
  168. NSLog(@"Failed to create symlink");
  169. return -1;
  170. }
  171. AuthorizationFree(authorizationRef, kAuthorizationFlagDestroyRights);
  172. return 0;
  173. }