Unverified Commit 89568e80 authored by Serge Zaitsev's avatar Serge Zaitsev Committed by GitHub

Merge pull request #4 from jslegendre/master

macOS version re-written with objc-runtime
parents 3b6520f6 1b6e312b
......@@ -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
......
......@@ -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
......
......@@ -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>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment