diff --git a/Makefile b/Makefile index fdfdc7c8f4cf36e68f8bb2a1352a79c24d080c19..483c0aa49a6ea0484ef722b8dd0b84869db18c28 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ else ifeq ($(shell uname -s),Linux) TRAY_CFLAGS := -DTRAY_APPINDICATOR=1 $(shell pkg-config --cflags appindicator3-0.1) TRAY_LDFLAGS := $(shell pkg-config --libs appindicator3-0.1) else ifeq ($(shell uname -s),Darwin) - TRAY_CFLAGS := -DTRAY_APPKIT=1 -x objective-c + TRAY_CFLAGS := -DTRAY_APPKIT=1 TRAY_LDFLAGS := -framework Cocoa endif diff --git a/README.md b/README.md index d57b48e01e3391d35f6d298e2de0614205b0c882..1ec6d2356e48316ee1576bf28ae15371ecca116e 100644 --- a/README.md +++ b/README.md @@ -136,7 +136,7 @@ array must have text field set to NULL. * [x] Checked/unchecked menu items * [x] Nested menus * [ ] Icons for menu items -* [ ] Rewrite ObjC code in C using ObjC Runtime (now ObjC code breaks many linters and static analyzers) +* [x] Rewrite ObjC code in C using ObjC Runtime (now ObjC code breaks many linters and static analyzers) * [ ] Call GTK code using dlopen/dlsym (to make binaries run safely if Gtk libraries are not available) ## License diff --git a/tray.h b/tray.h index 89b313d4dbbcfce74bb13ed6461f71e4c7b44036..fac1739054a5828a6fd30f0b83b2e2fc14603871 100644 --- a/tray.h +++ b/tray.h @@ -90,92 +90,118 @@ static void tray_exit() { loop_result = -1; } #elif defined(TRAY_APPKIT) -#import <Cocoa/Cocoa.h> +#include <objc/objc-runtime.h> +#include <limits.h> -static NSAutoreleasePool *pool; -static NSStatusBar *statusBar; +static id app; +static id pool; +static id statusBar; static id statusItem; static id statusBarButton; -@interface Tray : NSObject <NSApplicationDelegate> -- (void)menuCallback:(id)sender; -@end -@implementation Tray -- (void)menuCallback:(id)sender { - struct tray_menu *m = - (struct tray_menu *)[[sender representedObject] pointerValue]; - if (m != NULL && m->cb != NULL) { - m->cb(m); - } -} -@end - -static NSMenu *_tray_menu(struct tray_menu *m) { - NSMenu *menu = [NSMenu new]; - [menu autorelease]; - [menu setAutoenablesItems:NO]; - for (; m != NULL && m->text != NULL; m++) { - if (strcmp(m->text, "-") == 0) { - [menu addItem:[NSMenuItem separatorItem]]; - } else { - NSMenuItem *menuItem = [NSMenuItem alloc]; - [menuItem autorelease]; - [menuItem initWithTitle:[NSString stringWithUTF8String:m->text] - action:@selector(menuCallback:) - keyEquivalent:@""]; - [menuItem setEnabled:(m->disabled ? NO : YES)]; - [menuItem setState:(m->checked ? NSOnState : NSOffState)]; - [menuItem setRepresentedObject:[NSValue valueWithPointer:m]]; - - [menu addItem:menuItem]; +static id _tray_menu(struct tray_menu *m) { + id menu = objc_msgSend((id)objc_getClass("NSMenu"), sel_registerName("new")); + objc_msgSend(menu, sel_registerName("autorelease")); + objc_msgSend(menu, sel_registerName("setAutoenablesItems:"), false); - if (m->submenu != NULL) { - [menu setSubmenu:_tray_menu(m->submenu) forItem:menuItem]; + for (; m != NULL && m->text != NULL; m++) { + if (strcmp(m->text, "-") == 0) { + objc_msgSend(menu, sel_registerName("addItem:"), + objc_msgSend((id)objc_getClass("NSMenuItem"), sel_registerName("separatorItem"))); + } else { + id menuItem = objc_msgSend((id)objc_getClass("NSMenuItem"), sel_registerName("alloc")); + objc_msgSend(menuItem, sel_registerName("autorelease")); + objc_msgSend(menuItem, sel_registerName("initWithTitle:action:keyEquivalent:"), + objc_msgSend((id)objc_getClass("NSString"), sel_registerName("stringWithUTF8String:"), m->text), + sel_registerName("menuCallback:"), + objc_msgSend((id)objc_getClass("NSString"), sel_registerName("stringWithUTF8String:"), "")); + + objc_msgSend(menuItem, sel_registerName("setEnabled:"), (m->disabled ? false : true)); + objc_msgSend(menuItem, sel_registerName("setState:"), (m->checked ? 1 : 0)); + objc_msgSend(menuItem, sel_registerName("setRepresentedObject:"), + objc_msgSend((id)objc_getClass("NSValue"), sel_registerName("valueWithPointer:"), m)); + + objc_msgSend(menu, sel_registerName("addItem:"), menuItem); + + if (m->submenu != NULL) { + objc_msgSend(menu, sel_registerName("setSubmenu:forItem:"), _tray_menu(m->submenu), menuItem); } } } + return menu; } -static int tray_init(struct tray *tray) { - pool = [NSAutoreleasePool new]; - [NSApplication sharedApplication]; - - Tray *trayDelegate = [Tray new]; - [NSApp setDelegate:trayDelegate]; +static void menu_callback(id self, SEL cmd, id sender) { + struct tray_menu *m = + (struct tray_menu *)objc_msgSend(objc_msgSend(sender, sel_registerName("representedObject")), + sel_registerName("pointerValue")); - statusBar = [NSStatusBar systemStatusBar]; - statusItem = [statusBar statusItemWithLength:NSVariableStatusItemLength]; - [statusItem retain]; - [statusItem setHighlightMode:YES]; - statusBarButton = [statusItem button]; + if (m != NULL && m->cb != NULL) { + m->cb(m); + } +} - tray_update(tray); - [NSApp activateIgnoringOtherApps:YES]; - return 0; +static int tray_init(struct tray *tray) { + pool = objc_msgSend((id)objc_getClass("NSAutoreleasePool"), + sel_registerName("new")); + + objc_msgSend((id)objc_getClass("NSApplication"), + sel_registerName("sharedApplication")); + + Class trayDelegateClass = objc_allocateClassPair(objc_getClass("NSObject"), "Tray", 0); + class_addProtocol(trayDelegateClass, objc_getProtocol("NSApplicationDelegate")); + class_addMethod(trayDelegateClass, sel_registerName("menuCallback:"), (IMP)menu_callback, "v@:@"); + objc_registerClassPair(trayDelegateClass); + + id trayDelegate = objc_msgSend((id)trayDelegateClass, + sel_registerName("new")); + + app = objc_msgSend((id)objc_getClass("NSApplication"), + sel_registerName("sharedApplication")); + + objc_msgSend(app, sel_registerName("setDelegate:"), trayDelegate); + + statusBar = objc_msgSend((id)objc_getClass("NSStatusBar"), + sel_registerName("systemStatusBar")); + + statusItem = objc_msgSend(statusBar, sel_registerName("statusItemWithLength:"), -1.0); + + objc_msgSend(statusItem, sel_registerName("retain")); + objc_msgSend(statusItem, sel_registerName("setHighlightMode:"), true); + statusBarButton = objc_msgSend(statusItem, sel_registerName("button")); + tray_update(tray); + objc_msgSend(app, sel_registerName("activateIgnoringOtherApps:"), true); + return 0; } static int tray_loop(int blocking) { - NSEvent *event; - NSDate *until = (blocking ? [NSDate distantFuture] : [NSDate distantPast]); - event = [NSApp nextEventMatchingMask:NSAnyEventMask - untilDate:until - inMode:NSDefaultRunLoopMode - dequeue:YES]; - if (event) { - [NSApp sendEvent:event]; - } - return 0; + id until = (blocking ? + objc_msgSend((id)objc_getClass("NSDate"), sel_registerName("distantFuture")) : + objc_msgSend((id)objc_getClass("NSDate"), sel_registerName("distantPast"))); + + id event = objc_msgSend(app, sel_registerName("nextEventMatchingMask:untilDate:inMode:dequeue:"), + ULONG_MAX, + until, + objc_msgSend((id)objc_getClass("NSString"), + sel_registerName("stringWithUTF8String:"), + "kCFRunLoopDefaultMode"), + true); + if (event) { + objc_msgSend(app, sel_registerName("sendEvent:"), event); + } + return 0; } static void tray_update(struct tray *tray) { - [statusBarButton - setImage:[NSImage imageNamed:[NSString stringWithUTF8String:tray->icon]]]; + objc_msgSend(statusBarButton, sel_registerName("setImage:"), + objc_msgSend((id)objc_getClass("NSImage"), sel_registerName("imageNamed:"), + objc_msgSend((id)objc_getClass("NSString"), sel_registerName("stringWithUTF8String:"), tray->icon))); - [statusItem setMenu:_tray_menu(tray->menu)]; + objc_msgSend(statusItem, sel_registerName("setMenu:"), _tray_menu(tray->menu)); } -static void tray_exit() { [NSApp terminate:NSApp]; } +static void tray_exit() { objc_msgSend(app, sel_registerName("terminate:"), app); } #elif defined(TRAY_WINAPI) #include <windows.h>