systray_darwin.m 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
  1. #import <Cocoa/Cocoa.h>
  2. #include "systray.h"
  3. #if __MAC_OS_X_VERSION_MIN_REQUIRED < 101400
  4. #ifndef NSControlStateValueOff
  5. #define NSControlStateValueOff NSOffState
  6. #endif
  7. #ifndef NSControlStateValueOn
  8. #define NSControlStateValueOn NSOnState
  9. #endif
  10. #endif
  11. @interface MenuItem : NSObject
  12. {
  13. @public
  14. NSNumber* menuId;
  15. NSNumber* parentMenuId;
  16. NSString* title;
  17. NSString* tooltip;
  18. short disabled;
  19. short checked;
  20. }
  21. -(id) initWithId: (int)theMenuId
  22. withParentMenuId: (int)theParentMenuId
  23. withTitle: (const char*)theTitle
  24. withTooltip: (const char*)theTooltip
  25. withDisabled: (short)theDisabled
  26. withChecked: (short)theChecked;
  27. @end
  28. @implementation MenuItem
  29. -(id) initWithId: (int)theMenuId
  30. withParentMenuId: (int)theParentMenuId
  31. withTitle: (const char*)theTitle
  32. withTooltip: (const char*)theTooltip
  33. withDisabled: (short)theDisabled
  34. withChecked: (short)theChecked
  35. {
  36. menuId = [NSNumber numberWithInt:theMenuId];
  37. parentMenuId = [NSNumber numberWithInt:theParentMenuId];
  38. title = [[NSString alloc] initWithCString:theTitle
  39. encoding:NSUTF8StringEncoding];
  40. tooltip = [[NSString alloc] initWithCString:theTooltip
  41. encoding:NSUTF8StringEncoding];
  42. disabled = theDisabled;
  43. checked = theChecked;
  44. return self;
  45. }
  46. @end
  47. @interface AppDelegate: NSObject <NSApplicationDelegate>
  48. - (void) add_or_update_menu_item:(MenuItem*) item;
  49. - (IBAction)menuHandler:(id)sender;
  50. @property (assign) IBOutlet NSWindow *window;
  51. @end
  52. @implementation AppDelegate
  53. {
  54. NSStatusItem *statusItem;
  55. NSMenu *menu;
  56. NSCondition* cond;
  57. }
  58. @synthesize window = _window;
  59. - (void)applicationDidFinishLaunching:(NSNotification *)aNotification
  60. {
  61. self->statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength];
  62. self->menu = [[NSMenu alloc] init];
  63. [self->menu setAutoenablesItems: FALSE];
  64. [self->statusItem setMenu:self->menu];
  65. systray_ready();
  66. }
  67. - (void)applicationWillTerminate:(NSNotification *)aNotification
  68. {
  69. systray_on_exit();
  70. }
  71. - (void)setIcon:(NSImage *)image {
  72. statusItem.button.image = image;
  73. [self updateTitleButtonStyle];
  74. }
  75. - (void)setTitle:(NSString *)title {
  76. statusItem.button.title = title;
  77. [self updateTitleButtonStyle];
  78. }
  79. -(void)updateTitleButtonStyle {
  80. if (statusItem.button.image != nil) {
  81. if ([statusItem.button.title length] == 0) {
  82. statusItem.button.imagePosition = NSImageOnly;
  83. } else {
  84. statusItem.button.imagePosition = NSImageLeft;
  85. }
  86. } else {
  87. statusItem.button.imagePosition = NSNoImage;
  88. }
  89. }
  90. - (void)setTooltip:(NSString *)tooltip {
  91. statusItem.button.toolTip = tooltip;
  92. }
  93. - (IBAction)menuHandler:(id)sender
  94. {
  95. NSNumber* menuId = [sender representedObject];
  96. systray_menu_item_selected(menuId.intValue);
  97. }
  98. - (void)add_or_update_menu_item:(MenuItem *)item {
  99. NSMenu *theMenu = self->menu;
  100. NSMenuItem *parentItem;
  101. if ([item->parentMenuId integerValue] > 0) {
  102. parentItem = find_menu_item(menu, item->parentMenuId);
  103. if (parentItem.hasSubmenu) {
  104. theMenu = parentItem.submenu;
  105. } else {
  106. theMenu = [[NSMenu alloc] init];
  107. [theMenu setAutoenablesItems:NO];
  108. [parentItem setSubmenu:theMenu];
  109. }
  110. }
  111. NSMenuItem *menuItem;
  112. menuItem = find_menu_item(theMenu, item->menuId);
  113. if (menuItem == NULL) {
  114. menuItem = [theMenu addItemWithTitle:item->title
  115. action:@selector(menuHandler:)
  116. keyEquivalent:@""];
  117. [menuItem setRepresentedObject:item->menuId];
  118. }
  119. [menuItem setTitle:item->title];
  120. [menuItem setTag:[item->menuId integerValue]];
  121. [menuItem setTarget:self];
  122. [menuItem setToolTip:item->tooltip];
  123. if (item->disabled == 1) {
  124. menuItem.enabled = FALSE;
  125. } else {
  126. menuItem.enabled = TRUE;
  127. }
  128. if (item->checked == 1) {
  129. menuItem.state = NSControlStateValueOn;
  130. } else {
  131. menuItem.state = NSControlStateValueOff;
  132. }
  133. }
  134. NSMenuItem *find_menu_item(NSMenu *ourMenu, NSNumber *menuId) {
  135. NSMenuItem *foundItem = [ourMenu itemWithTag:[menuId integerValue]];
  136. if (foundItem != NULL) {
  137. return foundItem;
  138. }
  139. NSArray *menu_items = ourMenu.itemArray;
  140. int i;
  141. for (i = 0; i < [menu_items count]; i++) {
  142. NSMenuItem *i_item = [menu_items objectAtIndex:i];
  143. if (i_item.hasSubmenu) {
  144. foundItem = find_menu_item(i_item.submenu, menuId);
  145. if (foundItem != NULL) {
  146. return foundItem;
  147. }
  148. }
  149. }
  150. return NULL;
  151. };
  152. - (void) add_separator:(NSNumber*) parentMenuId
  153. {
  154. if (parentMenuId.integerValue != 0) {
  155. NSMenuItem* menuItem = find_menu_item(menu, parentMenuId);
  156. if (menuItem != NULL) {
  157. [menuItem.submenu addItem: [NSMenuItem separatorItem]];
  158. return;
  159. }
  160. }
  161. [menu addItem: [NSMenuItem separatorItem]];
  162. }
  163. - (void) hide_menu_item:(NSNumber*) menuId
  164. {
  165. NSMenuItem* menuItem = find_menu_item(menu, menuId);
  166. if (menuItem != NULL) {
  167. [menuItem setHidden:TRUE];
  168. }
  169. }
  170. - (void) setMenuItemIcon:(NSArray*)imageAndMenuId {
  171. NSImage* image = [imageAndMenuId objectAtIndex:0];
  172. NSNumber* menuId = [imageAndMenuId objectAtIndex:1];
  173. NSMenuItem* menuItem;
  174. menuItem = find_menu_item(menu, menuId);
  175. if (menuItem == NULL) {
  176. return;
  177. }
  178. menuItem.image = image;
  179. }
  180. - (void) show_menu_item:(NSNumber*) menuId
  181. {
  182. NSMenuItem* menuItem = find_menu_item(menu, menuId);
  183. if (menuItem != NULL) {
  184. [menuItem setHidden:FALSE];
  185. }
  186. }
  187. - (void) remove_menu_item:(NSNumber*) menuId
  188. {
  189. NSMenuItem* menuItem = find_menu_item(menu, menuId);
  190. if (menuItem != NULL) {
  191. [menuItem.menu removeItem:menuItem];
  192. }
  193. }
  194. - (void) reset_menu
  195. {
  196. [self->menu removeAllItems];
  197. }
  198. - (void) quit
  199. {
  200. [NSApp terminate:self];
  201. }
  202. @end
  203. bool internalLoop = false;
  204. AppDelegate *owner;
  205. void setInternalLoop(bool i) {
  206. internalLoop = i;
  207. }
  208. void registerSystray(void) {
  209. if (!internalLoop) { // with an external loop we don't take ownership of the app
  210. return;
  211. }
  212. owner = [[AppDelegate alloc] init];
  213. [[NSApplication sharedApplication] setDelegate:owner];
  214. // A workaround to avoid crashing on macOS versions before Catalina. Somehow
  215. // SIGSEGV would happen inside AppKit if [NSApp run] is called from a
  216. // different function, even if that function is called right after this.
  217. if (floor(NSAppKitVersionNumber) <= /*NSAppKitVersionNumber10_14*/ 1671){
  218. [NSApp run];
  219. }
  220. }
  221. void nativeEnd(void) {
  222. systray_on_exit();
  223. }
  224. int nativeLoop(void) {
  225. if (floor(NSAppKitVersionNumber) > /*NSAppKitVersionNumber10_14*/ 1671){
  226. [NSApp run];
  227. }
  228. return EXIT_SUCCESS;
  229. }
  230. void nativeStart(void) {
  231. owner = [[AppDelegate alloc] init];
  232. NSNotification *launched = [NSNotification notificationWithName:NSApplicationDidFinishLaunchingNotification
  233. object:[NSApplication sharedApplication]];
  234. [owner applicationDidFinishLaunching:launched];
  235. }
  236. void runInMainThread(SEL method, id object) {
  237. [owner
  238. performSelectorOnMainThread:method
  239. withObject:object
  240. waitUntilDone: YES];
  241. }
  242. void setIcon(const char* iconBytes, int length, bool template) {
  243. NSData* buffer = [NSData dataWithBytes: iconBytes length:length];
  244. @autoreleasepool {
  245. NSImage *image = [[NSImage alloc] initWithData:buffer];
  246. [image setSize:NSMakeSize(16, 16)];
  247. image.template = template;
  248. runInMainThread(@selector(setIcon:), (id)image);
  249. }
  250. }
  251. void setMenuItemIcon(const char* iconBytes, int length, int menuId, bool template) {
  252. NSData* buffer = [NSData dataWithBytes: iconBytes length:length];
  253. @autoreleasepool {
  254. NSImage *image = [[NSImage alloc] initWithData:buffer];
  255. [image setSize:NSMakeSize(16, 16)];
  256. image.template = template;
  257. NSNumber *mId = [NSNumber numberWithInt:menuId];
  258. runInMainThread(@selector(setMenuItemIcon:), @[image, (id)mId]);
  259. }
  260. }
  261. void setTitle(char* ctitle) {
  262. NSString* title = [[NSString alloc] initWithCString:ctitle
  263. encoding:NSUTF8StringEncoding];
  264. free(ctitle);
  265. runInMainThread(@selector(setTitle:), (id)title);
  266. }
  267. void setTooltip(char* ctooltip) {
  268. NSString* tooltip = [[NSString alloc] initWithCString:ctooltip
  269. encoding:NSUTF8StringEncoding];
  270. free(ctooltip);
  271. runInMainThread(@selector(setTooltip:), (id)tooltip);
  272. }
  273. void add_or_update_menu_item(int menuId, int parentMenuId, char* title, char* tooltip, short disabled, short checked, short isCheckable) {
  274. MenuItem* item = [[MenuItem alloc] initWithId: menuId withParentMenuId: parentMenuId withTitle: title withTooltip: tooltip withDisabled: disabled withChecked: checked];
  275. free(title);
  276. free(tooltip);
  277. runInMainThread(@selector(add_or_update_menu_item:), (id)item);
  278. }
  279. void add_separator(int menuId, int parentId) {
  280. NSNumber *pId = [NSNumber numberWithInt:parentId];
  281. runInMainThread(@selector(add_separator:), (id)pId);
  282. }
  283. void hide_menu_item(int menuId) {
  284. NSNumber *mId = [NSNumber numberWithInt:menuId];
  285. runInMainThread(@selector(hide_menu_item:), (id)mId);
  286. }
  287. void remove_menu_item(int menuId) {
  288. NSNumber *mId = [NSNumber numberWithInt:menuId];
  289. runInMainThread(@selector(remove_menu_item:), (id)mId);
  290. }
  291. void show_menu_item(int menuId) {
  292. NSNumber *mId = [NSNumber numberWithInt:menuId];
  293. runInMainThread(@selector(show_menu_item:), (id)mId);
  294. }
  295. void reset_menu() {
  296. runInMainThread(@selector(reset_menu), nil);
  297. }
  298. void quit() {
  299. runInMainThread(@selector(quit), nil);
  300. }